diff --git a/.azure-pipelines/publish.yml b/.azure-pipelines/publish.yml
index c6ea71c696..dab5144ddf 100644
--- a/.azure-pipelines/publish.yml
+++ b/.azure-pipelines/publish.yml
@@ -1,4 +1,10 @@
-trigger: none # We don't want CI builds, just a manual release process
+# Run only on tags (no PRs, no branch CI)
+pr: none
+trigger:
+ tags:
+ include:
+ - '*'
+
parameters:
- name: doRelease
displayName: Push the Playwright Release to NuGet.org
@@ -12,9 +18,9 @@ parameters:
variables:
- name: BuildConfiguration
- value: 'Release'
+ value: 'Release'
- name: TeamName
- value: Playwright
+ value: 'Playwright'
resources:
repositories:
@@ -44,7 +50,7 @@ extends:
signing:
enabled: true
signType: real
- templateContext:
+ signWithProd: true
outputs:
- output: pipelineArtifact
displayName: 'Publish Artifact'
@@ -77,25 +83,30 @@ extends:
src/Playwright/Playwright.csproj;
src/Playwright.NUnit/Playwright.NUnit.csproj;
src/Playwright.MSTest/Playwright.MSTest.csproj;
+ src/Playwright.MSTest.v4/Playwright.MSTest.v4.csproj;
src/Playwright.Xunit/Playwright.Xunit.csproj;
+ src/Playwright.Xunit.v3/Playwright.Xunit.v3.csproj;
src/Playwright.TestAdapter/Playwright.TestAdapter.csproj;
packDirectory: '$(Build.ArtifactStagingDirectory)/nuget'
versioningScheme: 'off'
- task: 1ES.PublishNuget@1
displayName: Publish Microsoft.Playwright{NUnit,MSTest,Xunit,TestAdapter}
- condition: eq('${{parameters.doRelease}}', true)
+ condition: or(
+ eq('${{ parameters.doRelease }}', true),
+ startsWith(variables['Build.SourceBranch'], 'refs/tags/')
+ )
inputs:
useDotNetTask: false
# The reason for the 1.* after the package name is that we know the end of the package name in order to not
# match with e.g. Microsoft.Playwright.CLI when we only want to match Microsoft.Playwright.
# Semicolon separated as per https://portal.microsofticm.com/imp/v5/incidents/details/467483180/summary
- packagesToPush: $(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.MSTest.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.Xunit.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.NUnit.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.TestAdapter.1.*.nupkg
+ packagesToPush: $(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.MSTest.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.MSTest.v4.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.Xunit.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.Xunit.v3.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.NUnit.1.*.nupkg;$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.TestAdapter.1.*.nupkg
packageParentPath: '$(Build.ArtifactStagingDirectory)'
nuGetFeedType: external
publishFeedCredentials: 'NuGet-Playwright'
- task: 1ES.PublishNuget@1
displayName: Publish Microsoft.Playwright.CLI
- condition: eq('${{parameters.doReleaseCLI}}', true)
+ condition: eq('${{ parameters.doReleaseCLI }}', true)
inputs:
useDotNetTask: false
packagesToPush: '$(Build.ArtifactStagingDirectory)/nuget/Microsoft.Playwright.CLI.1.*.nupkg'
diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml
new file mode 100644
index 0000000000..3c650dcb4e
--- /dev/null
+++ b/.config/1espt/PipelineAutobaseliningConfig.yml
@@ -0,0 +1,23 @@
+## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details.
+
+pipelines:
+ 14588:
+ retail:
+ source:
+ credscan:
+ lastModifiedDate: 2025-08-12
+ eslint:
+ lastModifiedDate: 2025-08-12
+ psscriptanalyzer:
+ lastModifiedDate: 2025-08-12
+ armory:
+ lastModifiedDate: 2025-08-12
+ accessibilityinsights:
+ lastModifiedDate: 2025-08-12
+ binary:
+ credscan:
+ lastModifiedDate: 2025-08-12
+ binskim:
+ lastModifiedDate: 2025-08-12
+ spotbugs:
+ lastModifiedDate: 2025-08-12
diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines
new file mode 100644
index 0000000000..a56e1789cd
--- /dev/null
+++ b/.config/guardian/.gdnbaselines
@@ -0,0 +1,113 @@
+{
+ "properties": {
+ "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines"
+ },
+ "version": "1.0.0",
+ "baselines": {
+ "default": {
+ "name": "default",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "lastUpdatedDate": "2025-08-12 12:02:09Z"
+ }
+ },
+ "results": {
+ "b2c126d95a9b96964d18d3a60b8100823a08da6340de51db534197aea137861b": {
+ "signature": "b2c126d95a9b96964d18d3a60b8100823a08da6340de51db534197aea137861b",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/TestServer/key.pfx",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "109e68661121356b830572b5e4140939c7da48a90a6824db38785bd42f904d03": {
+ "signature": "109e68661121356b830572b5e4140939c7da48a90a6824db38785bd42f904d03",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/server/server_key.pem",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "a7f17efaa8efc6de4a0288d154928d8ae13dcd23350dc10bcb2ae54dcaf90878": {
+ "signature": "a7f17efaa8efc6de4a0288d154928d8ae13dcd23350dc10bcb2ae54dcaf90878",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/client/localhost/localhost.key",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "fd3fe59c06490e9b3f4cc72ed4cc7e4110ee3676bbfc2efe9c617566701923bf": {
+ "signature": "fd3fe59c06490e9b3f4cc72ed4cc7e4110ee3676bbfc2efe9c617566701923bf",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/client/self-signed/key.pem",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "9409e383d7ec985e0e5793d49c243881276800040b9e6686b1b464e6c92bd197": {
+ "signature": "9409e383d7ec985e0e5793d49c243881276800040b9e6686b1b464e6c92bd197",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/client/trusted/cert-legacy.pfx",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "a9f444c2c883c71914cfa934ab765cf6cef61b629195695750493f8198e01b8e": {
+ "signature": "a9f444c2c883c71914cfa934ab765cf6cef61b629195695750493f8198e01b8e",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/client/trusted/cert.pfx",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ },
+ "f65b0b3a90f82519d4a24aaa87ae2ec94a812c6fb10562045839de0e9ab1ea66": {
+ "signature": "f65b0b3a90f82519d4a24aaa87ae2ec94a812c6fb10562045839de0e9ab1ea66",
+ "alternativeSignatures": [],
+ "target": "src/Playwright.Tests/assets/client-certificates/client/trusted/key.pem",
+ "line": 1,
+ "memberOf": [
+ "default"
+ ],
+ "tool": "credscan",
+ "ruleId": "CSCAN-GENERAL0020",
+ "createdDate": "2025-08-12 12:02:09Z",
+ "expirationDate": "2026-01-29 12:52:02Z",
+ "justification": "This error is baselined with an expiration date of 180 days from 2025-08-12 12:52:02Z"
+ }
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml
index 8afcd9b802..bfbe0bbf10 100644
--- a/.github/workflows/code-style.yml
+++ b/.github/workflows/code-style.yml
@@ -12,7 +12,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/nuget-package-tests.yml b/.github/workflows/nuget-package-tests.yml
index bd214e827b..01be480991 100644
--- a/.github/workflows/nuget-package-tests.yml
+++ b/.github/workflows/nuget-package-tests.yml
@@ -17,7 +17,7 @@ jobs:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml
index 126b262b84..51635ba662 100644
--- a/.github/workflows/publish_docker.yml
+++ b/.github/workflows/publish_docker.yml
@@ -15,7 +15,7 @@ jobs:
contents: read # This is required for actions/checkout to succeed
environment: Docker
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Azure login
uses: azure/login@v2
with:
diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml
index c1e9aead4a..3a36f3438c 100644
--- a/.github/workflows/test_docker.yml
+++ b/.github/workflows/test_docker.yml
@@ -26,7 +26,7 @@ jobs:
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 60b18595c0..348c504875 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,7 +21,7 @@ jobs:
browser: [chromium, firefox, webkit]
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/tests_harness.yml b/.github/workflows/tests_harness.yml
index d9cc0d23f6..d1329404b0 100644
--- a/.github/workflows/tests_harness.yml
+++ b/.github/workflows/tests_harness.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/validate-nuget-packages.yml b/.github/workflows/validate-nuget-packages.yml
index 9140b5a16f..4cb06c1fd7 100644
--- a/.github/workflows/validate-nuget-packages.yml
+++ b/.github/workflows/validate-nuget-packages.yml
@@ -12,7 +12,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
diff --git a/README.md b/README.md
index b885a5d42d..88b3268d52 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
-| Chromium 139.0.7258.5 | ✅ | ✅ | ✅ |
+| Chromium 140.0.7339.16 | ✅ | ✅ | ✅ |
| WebKit 26.0 | ✅ | ✅ | ✅ |
-| Firefox 140.0.2 | ✅ | ✅ | ✅ |
+| Firefox 141.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 cb0728d9b5..d58172cf1d 100644
--- a/src/Common/Version.props
+++ b/src/Common/Version.props
@@ -1,8 +1,8 @@
- 1.54.0
+ 1.55.0$(AssemblyVersion)
- 1.54.1
+ 1.55.0-beta-1756314050000$(AssemblyVersion)$(AssemblyVersion)true
diff --git a/src/Playwright.CLI/Playwright.CLI.csproj b/src/Playwright.CLI/Playwright.CLI.csproj
index 91c522fbe2..b16c35413e 100644
--- a/src/Playwright.CLI/Playwright.CLI.csproj
+++ b/src/Playwright.CLI/Playwright.CLI.csproj
@@ -34,6 +34,7 @@
MIT
+ README.md
@@ -49,5 +50,6 @@
+
diff --git a/src/Playwright.MSTest.v4/Playwright.MSTest.v4.csproj b/src/Playwright.MSTest.v4/Playwright.MSTest.v4.csproj
new file mode 100644
index 0000000000..0b6ccd8420
--- /dev/null
+++ b/src/Playwright.MSTest.v4/Playwright.MSTest.v4.csproj
@@ -0,0 +1,52 @@
+
+
+
+ Microsoft.Playwright.MSTest.v4
+ Microsoft.Playwright.MSTest.v4
+ A set of helpers and fixtures to enable using Playwright in MSTest 4 tests.
+
+ Playwright enables reliable end-to-end testing for modern web apps. This package brings in additional helpers
+ and fixtures to enable using it within MSTest.
+
+ icon.png
+ netstandard2.0
+ true
+ true
+ Microsoft.Playwright.MSTest
+ 0.0.0
+ True
+ Microsoft.Playwright.MSTest.v4
+ ./nupkg
+ true
+ enable
+ README.md
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(FileName)%(Extension)
+
+
+
diff --git a/src/Playwright.MSTest/Playwright.MSTest.csproj b/src/Playwright.MSTest/Playwright.MSTest.csproj
index d9f257d80e..b00180aff4 100644
--- a/src/Playwright.MSTest/Playwright.MSTest.csproj
+++ b/src/Playwright.MSTest/Playwright.MSTest.csproj
@@ -19,6 +19,7 @@
./nupkgtrueenable
+ README.mdtrue1.52.0
@@ -43,5 +44,6 @@
+
diff --git a/src/Playwright.MSTest/WorkerAwareTest.cs b/src/Playwright.MSTest/WorkerAwareTest.cs
index 0bea2243c9..d333c5523b 100644
--- a/src/Playwright.MSTest/WorkerAwareTest.cs
+++ b/src/Playwright.MSTest/WorkerAwareTest.cs
@@ -10,7 +10,7 @@ namespace Microsoft.Playwright.MSTest;
public class WorkerAwareTest
{
- public TestContext TestContext { get; set; } = null!;
+ public virtual TestContext TestContext { get; set; } = null!;
private static readonly ConcurrentStack _allWorkers = new();
private Worker _currentWorker = null!;
diff --git a/src/Playwright.NUnit/Playwright.NUnit.csproj b/src/Playwright.NUnit/Playwright.NUnit.csproj
index b3aa923280..992a676455 100644
--- a/src/Playwright.NUnit/Playwright.NUnit.csproj
+++ b/src/Playwright.NUnit/Playwright.NUnit.csproj
@@ -19,6 +19,7 @@
./nupkgtrueenable
+ README.mdtrue1.52.0
@@ -43,5 +44,6 @@
+
diff --git a/src/Playwright.TestAdapter/Playwright.TestAdapter.csproj b/src/Playwright.TestAdapter/Playwright.TestAdapter.csproj
index e009cc0980..873863f844 100644
--- a/src/Playwright.TestAdapter/Playwright.TestAdapter.csproj
+++ b/src/Playwright.TestAdapter/Playwright.TestAdapter.csproj
@@ -18,6 +18,7 @@
./nupkgtrueenable
+ README.mdtrue1.52.0
@@ -40,5 +41,6 @@
+
diff --git a/src/Playwright.TestAdapter/PlaywrightSettingsProvider.cs b/src/Playwright.TestAdapter/PlaywrightSettingsProvider.cs
index 0b6b8872a9..58ee2594b1 100644
--- a/src/Playwright.TestAdapter/PlaywrightSettingsProvider.cs
+++ b/src/Playwright.TestAdapter/PlaywrightSettingsProvider.cs
@@ -23,6 +23,7 @@
*/
using System;
+using System.Text.Json;
using System.Xml;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
@@ -35,6 +36,22 @@ public class PlaywrightSettingsProvider : ISettingsProvider
{
private static PlaywrightSettingsXml? _settings = null!;
+ public static void LoadViaEnvIfNeeded()
+ {
+ if (_settings == null)
+ {
+ var settings = Environment.GetEnvironmentVariable("PW_INTERNAL_ADAPTER_SETTINGS");
+ if (!string.IsNullOrEmpty(settings))
+ {
+ _settings = JsonSerializer.Deserialize(settings);
+ }
+ else
+ {
+ _settings = new PlaywrightSettingsXml();
+ }
+ }
+ }
+
public static string BrowserName
{
get
@@ -102,5 +119,10 @@ private static void ValidateBrowserName(string browserName, string fromText, str
}
public void Load(XmlReader reader)
- => _settings = new PlaywrightSettingsXml(reader);
+ {
+ // NOTE: ISettingsProvider::Load is not called when there are no runsettings (either file or passed via command line).
+ _settings = new PlaywrightSettingsXml(reader);
+ Environment.SetEnvironmentVariable("PW_INTERNAL_ADAPTER_SETTINGS", JsonSerializer.Serialize(_settings));
+ }
}
+
diff --git a/src/Playwright.TestAdapter/PlaywrightSettingsXml.cs b/src/Playwright.TestAdapter/PlaywrightSettingsXml.cs
index dea4d983da..6e9371d982 100644
--- a/src/Playwright.TestAdapter/PlaywrightSettingsXml.cs
+++ b/src/Playwright.TestAdapter/PlaywrightSettingsXml.cs
@@ -35,6 +35,10 @@ namespace Microsoft.Playwright.TestAdapter;
public class PlaywrightSettingsXml
{
+ public PlaywrightSettingsXml()
+ {
+ }
+
public PlaywrightSettingsXml(XmlReader reader)
{
// Skip Playwright root Element
@@ -159,10 +163,10 @@ private static object ParseAsJson(string value, Type type)
return JsonSerializer.Deserialize(value.Replace('\'', '"'), type)!;
}
- public BrowserTypeLaunchOptions? LaunchOptions { get; private set; }
- public string? BrowserName { get; private set; }
- public bool? Headless { get; private set; }
- public float? ExpectTimeout { get; private set; }
- public int? Retries { get; private set; }
+ public BrowserTypeLaunchOptions? LaunchOptions { get; set; }
+ public string? BrowserName { get; set; }
+ public bool? Headless { get; set; }
+ public float? ExpectTimeout { get; set; }
+ public int? Retries { get; set; }
}
diff --git a/src/Playwright.TestingHarnessTest/Playwright.TestingHarnessTest.csproj b/src/Playwright.TestingHarnessTest/Playwright.TestingHarnessTest.csproj
index a553b29050..2bc474ac12 100644
--- a/src/Playwright.TestingHarnessTest/Playwright.TestingHarnessTest.csproj
+++ b/src/Playwright.TestingHarnessTest/Playwright.TestingHarnessTest.csproj
@@ -11,13 +11,18 @@
-
+
+
+
+
+
+
@@ -26,6 +31,11 @@
+
+
+
+
+
diff --git a/src/Playwright.TestingHarnessTest/package-lock.json b/src/Playwright.TestingHarnessTest/package-lock.json
index f4aea272a6..3f13a54fad 100644
--- a/src/Playwright.TestingHarnessTest/package-lock.json
+++ b/src/Playwright.TestingHarnessTest/package-lock.json
@@ -7,19 +7,19 @@
"": {
"name": "playwright.testingharnesstest",
"devDependencies": {
- "@playwright/test": "1.54.1",
+ "@playwright/test": "1.55.0-beta-1756314050000",
"@types/node": "^22.12.0",
"fast-xml-parser": "^4.5.0"
}
},
"node_modules/@playwright/test": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
- "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-Iqiy5YO7fNGaUcjZSBn9NHnAjEIROVsUzfbHKbg28WDudgMu5KTpk3nh39WTbRJoamSgfECGmuMTJMuwgyfgWw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.54.1"
+ "playwright": "1.55.0-beta-1756314050000"
},
"bin": {
"playwright": "cli.js"
@@ -77,13 +77,13 @@
}
},
"node_modules/playwright": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
- "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-dJJmaVqz+z5UqvU8S4yex796OSGqyp4ej7py+T5t6YjN6NeHW8gqHy8uaDrxKs7i7PXn+60LCbhRIM/rCN2NIA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.54.1"
+ "playwright-core": "1.55.0-beta-1756314050000"
},
"bin": {
"playwright": "cli.js"
@@ -96,9 +96,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
- "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-jgAGIr1Vbgkb984uxj5JBngw53Gygp1u6PNF4X52+Wo4Q0PfEj4mUwgomm+AC3g8LEze4cR7Lk9+oQRUq7as2Q==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -124,12 +124,12 @@
},
"dependencies": {
"@playwright/test": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
- "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-Iqiy5YO7fNGaUcjZSBn9NHnAjEIROVsUzfbHKbg28WDudgMu5KTpk3nh39WTbRJoamSgfECGmuMTJMuwgyfgWw==",
"dev": true,
"requires": {
- "playwright": "1.54.1"
+ "playwright": "1.55.0-beta-1756314050000"
}
},
"@types/node": {
@@ -158,19 +158,19 @@
"optional": true
},
"playwright": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
- "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-dJJmaVqz+z5UqvU8S4yex796OSGqyp4ej7py+T5t6YjN6NeHW8gqHy8uaDrxKs7i7PXn+60LCbhRIM/rCN2NIA==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
- "playwright-core": "1.54.1"
+ "playwright-core": "1.55.0-beta-1756314050000"
}
},
"playwright-core": {
- "version": "1.54.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
- "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
+ "version": "1.55.0-beta-1756314050000",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0-beta-1756314050000.tgz",
+ "integrity": "sha512-jgAGIr1Vbgkb984uxj5JBngw53Gygp1u6PNF4X52+Wo4Q0PfEj4mUwgomm+AC3g8LEze4cR7Lk9+oQRUq7as2Q==",
"dev": true
},
"strnum": {
diff --git a/src/Playwright.TestingHarnessTest/package.json b/src/Playwright.TestingHarnessTest/package.json
index 195bd6cd22..89b302912c 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.54.1",
+ "@playwright/test": "1.55.0-beta-1756314050000",
"@types/node": "^22.12.0",
"fast-xml-parser": "^4.5.0"
}
diff --git a/src/Playwright.TestingHarnessTest/tests/baseTest.ts b/src/Playwright.TestingHarnessTest/tests/baseTest.ts
index 968eba1dc9..55e850795c 100644
--- a/src/Playwright.TestingHarnessTest/tests/baseTest.ts
+++ b/src/Playwright.TestingHarnessTest/tests/baseTest.ts
@@ -19,9 +19,10 @@ type RunResult = {
export const test = base.extend<{
proxyServer: ProxyServer;
- testMode: 'nunit' | 'mstest' | 'xunit';
+ testMode: 'nunit' | 'mstest' | 'mstest.v4' | 'xunit' | 'xunit.v3';
runTest: (files: Record, command: string, env?: NodeJS.ProcessEnv) => Promise;
- launchServer: ({ port: number }) => Promise;
+ launchServer: (options: { port: number }) => Promise;
+ server: SimpleServer;
}>({
proxyServer: async ({}, use) => {
const proxyServer = new ProxyServer();
@@ -29,6 +30,12 @@ export const test = base.extend<{
await use(proxyServer);
await proxyServer.stop();
},
+ server: async ({}, use) => {
+ const server = new SimpleServer();
+ await server.listen();
+ await use(server);
+ await server.stop();
+ },
testMode: null,
launchServer: async ({ playwright }, use) => {
const servers: BrowserServer[] = [];
@@ -183,4 +190,38 @@ class ProxyServer {
}
}
+class SimpleServer {
+ private _server: http.Server;
+
+ constructor() {
+ this._server = http.createServer(this.handler.bind(this));
+ }
+
+ handler(req: http.IncomingMessage, res: http.ServerResponse) {
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(`
+
+
+ Test Server
+
+
+
Test Server
+
This is a simple test server for Playwright tests.
+
+`);
+ }
+
+ get EMPTY_PAGE() {
+ return `http://127.0.0.1:${(this._server.address() as AddressInfo).port}/empty.html`;
+ }
+
+ async listen() {
+ await new Promise(resolve => this._server.listen(0, resolve));
+ }
+
+ async stop() {
+ await new Promise(resolve => this._server.close(() => resolve()));
+ }
+}
+
export { expect } from '@playwright/test';
diff --git a/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/mstest.spec.ts
similarity index 98%
rename from src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts
rename to src/Playwright.TestingHarnessTest/tests/mstest.spec.ts
index c1d8e1ae6b..83ba2685eb 100644
--- a/src/Playwright.TestingHarnessTest/tests/mstest/basic.spec.ts
+++ b/src/Playwright.TestingHarnessTest/tests/mstest.spec.ts
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-import { test, expect } from '../baseTest';
+import { test, expect } from './baseTest';
test.use({ testMode: 'mstest' });
@@ -223,7 +223,7 @@ test('should be able to parse BrowserName and LaunchOptions.Headless from runset
expect(result.stdout).not.toContain("Headless")
});
-test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer }) => {
+test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -240,7 +240,7 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
public async Task Test()
{
Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
- await Page.GotoAsync("http://example.com");
+ await Page.GotoAsync("${server.EMPTY_PAGE}");
}
}`,
'.runsettings': `
@@ -266,8 +266,8 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
expect(result.stdout).not.toContain("Headless");
- const { url, auth } = proxyServer.requests.find(r => r.url === 'http://example.com/')!;;
- expect(url).toBe('http://example.com/');
+ const { url, auth } = proxyServer.requests.find(r => r.url === server.EMPTY_PAGE)!;
+ expect(url).toBe(server.EMPTY_PAGE);
expect(auth).toBe('user:pwd');
});
@@ -307,7 +307,7 @@ test('should be able to parse LaunchOptions.Args from runsettings', async ({ run
expect(result.stdout).toContain("User-Agent: hello")
});
-test('should be able to override context options', async ({ runTest }) => {
+test('should be able to override context options', async ({ runTest, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -335,7 +335,7 @@ test('should be able to override context options', async ({ runTest }) => {
Assert.AreEqual("Foobar", await Page.EvaluateAsync("() => navigator.userAgent"));
- var response = await Page.GotoAsync("https://example.com/");
+ var response = await Page.GotoAsync("${server.EMPTY_PAGE}");
Assert.AreEqual(await response.Request.HeaderValueAsync("Kekstar"), "KekStarValue");
}
diff --git a/src/Playwright.TestingHarnessTest/tests/mstest.v4.spec.ts b/src/Playwright.TestingHarnessTest/tests/mstest.v4.spec.ts
new file mode 100644
index 0000000000..a4904443b1
--- /dev/null
+++ b/src/Playwright.TestingHarnessTest/tests/mstest.v4.spec.ts
@@ -0,0 +1,537 @@
+/*
+ * 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.
+ */
+
+import { test, expect } from './baseTest';
+
+test.use({ testMode: 'mstest.v4' });
+
+test('should be able to forward DEBUG=pw:api env var', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ try
+ {
+ await Page.Locator("button").ClickAsync(new() { Timeout = 1_000 });
+ }
+ catch
+ {
+ }
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ pw:api
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stderr).toContain("pw:api")
+ expect(result.stderr).toContain("element is not enabled")
+ expect(result.stderr).toContain("retrying click action")
+});
+
+test('should be able to set the browser via the runsettings file', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("BrowserName: " + BrowserName);
+ Console.WriteLine("BrowserType: " + BrowserType.Name);
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ webkit
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: webkit")
+ expect(result.stdout).toContain("BrowserType: webkit")
+ expect(/User-Agent: .*WebKit.*/.test(result.stdout)).toBeTruthy()
+});
+
+test('should prioritize browser from env over the runsettings file', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("BrowserName: " + BrowserName);
+ Console.WriteLine("BrowserType: " + BrowserType.Name);
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ webkit
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings', {
+ BROWSER: 'firefox'
+ });
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: firefox")
+ expect(result.stdout).toContain("BrowserType: firefox")
+ expect(/User-Agent: .*Firefox.*/.test(result.stdout)).toBeTruthy()
+});
+
+test('should be able to make the browser headed via the env', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("BrowserName: " + BrowserName);
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ }, 'dotnet test', {
+ HEADED: '1'
+ });
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: chromium")
+ expect(result.stdout).not.toContain("Headless")
+});
+
+test('should be able to parse BrowserName and LaunchOptions.Headless from runsettings', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("BrowserName: " + BrowserName);
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ false
+
+
+ firefox
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: firefox")
+ expect(result.stdout).not.toContain("Headless")
+});
+
+test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer, server }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ await Page.GotoAsync("${server.EMPTY_PAGE}");
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ chromium
+
+ false
+
+ ${proxyServer.listenAddr()}
+ user
+ pwd
+
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+
+ expect(result.stdout).not.toContain("Headless");
+
+ const { url, auth } = proxyServer.requests.find(r => r.url === server.EMPTY_PAGE)!;
+ expect(url).toBe(server.EMPTY_PAGE);
+ expect(auth).toBe('user:pwd');
+});
+
+test('should be able to parse LaunchOptions.Args from runsettings', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ ['--user-agent=hello']
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("User-Agent: hello")
+});
+
+test('should be able to override context options', async ({ runTest, server }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+
+ Assert.IsFalse(await Page.EvaluateAsync("() => matchMedia('(prefers-color-scheme: light)').matches"));
+ Assert.IsTrue(await Page.EvaluateAsync("() => matchMedia('(prefers-color-scheme: dark)').matches"));
+
+ Assert.AreEqual(1920, await Page.EvaluateAsync("() => window.innerWidth"));
+ Assert.AreEqual(1080, await Page.EvaluateAsync("() => window.innerHeight"));
+
+ Assert.AreEqual("Foobar", await Page.EvaluateAsync("() => navigator.userAgent"));
+
+ var response = await Page.GotoAsync("${server.EMPTY_PAGE}");
+ Assert.AreEqual(await response.Request.HeaderValueAsync("Kekstar"), "KekStarValue");
+ }
+
+ public override BrowserNewContextOptions ContextOptions()
+ {
+ return new BrowserNewContextOptions()
+ {
+ ColorScheme = ColorScheme.Dark,
+ UserAgent = "Foobar",
+ ViewportSize = new()
+ {
+ Width = 1920,
+ Height = 1080
+ },
+ ExtraHTTPHeaders = new Dictionary {
+ { "Kekstar", "KekStarValue" }
+ }
+ };
+ }
+ }`}, 'dotnet test');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+});
+
+test('should be able to override launch options', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ false
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).not.toContain("Headless");
+});
+
+test.describe('Expect() timeout', () => {
+ test('should have 5 seconds by default', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text");
+ }
+ }`,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 5000ms")
+ });
+
+ test('should be able to override it via each Expect() call', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text", new() { Timeout = 100 });
+ }
+ }`,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ 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({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text");
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ 123
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 123ms")
+ });
+});
+
+test.describe('ConnectOptions', () => {
+ const ExampleTestWithConnectOptions = `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.MSTest;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ namespace Playwright.TestingHarnessTest.MSTest;
+
+ [TestClass]
+ public class : PageTest
+ {
+ [TestMethod]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ }
+ public override async Task<(string, BrowserTypeConnectOptions)?> ConnectOptionsAsync()
+ {
+ return ("http://127.0.0.1:1234", null);
+ }
+ }`;
+
+ test('should fail when the server is not reachable', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': ExampleTestWithConnectOptions,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain('connect ECONNREFUSED 127.0.0.1:1234')
+ });
+
+ test('should pass when the server is reachable', async ({ runTest, launchServer }) => {
+ await launchServer({ port: 1234 });
+ const result = await runTest({
+ 'ExampleTests.cs': ExampleTestWithConnectOptions,
+ }, 'dotnet test');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ });
+});
diff --git a/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/nunit.spec.ts
similarity index 98%
rename from src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts
rename to src/Playwright.TestingHarnessTest/tests/nunit.spec.ts
index 995ce49b3f..4d79652803 100644
--- a/src/Playwright.TestingHarnessTest/tests/nunit/basic.spec.ts
+++ b/src/Playwright.TestingHarnessTest/tests/nunit.spec.ts
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-import { test, expect } from '../baseTest';
+import { test, expect } from './baseTest';
test.use({ testMode: 'nunit' });
@@ -221,7 +221,7 @@ test('should be able to parse BrowserName and LaunchOptions.Headless from runset
expect(result.stdout).not.toContain("Headless")
});
-test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer }) => {
+test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -237,7 +237,7 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
public async Task Test()
{
Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
- await Page.GotoAsync("http://example.com");
+ await Page.GotoAsync("${server.EMPTY_PAGE}");
}
}`,
'.runsettings': `
@@ -263,8 +263,8 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
expect(result.stdout).not.toContain("Headless");
- const { url, auth } = proxyServer.requests.find(r => r.url === 'http://example.com/')!;;
- expect(url).toBe('http://example.com/');
+ const { url, auth } = proxyServer.requests.find(r => r.url === server.EMPTY_PAGE)!;
+ expect(url).toBe(server.EMPTY_PAGE);
expect(auth).toBe('user:pwd');
});
@@ -303,7 +303,7 @@ test('should be able to parse LaunchOptions.Args from runsettings', async ({ run
expect(result.stdout).toContain("User-Agent: hello")
});
-test('should be able to override context options', async ({ runTest }) => {
+test('should be able to override context options', async ({ runTest, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -330,7 +330,7 @@ test('should be able to override context options', async ({ runTest }) => {
Assert.AreEqual("Foobar", await Page.EvaluateAsync("() => navigator.userAgent"));
- var response = await Page.GotoAsync("https://example.com/");
+ var response = await Page.GotoAsync("${server.EMPTY_PAGE}");
Assert.AreEqual(await response.Request.HeaderValueAsync("Kekstar"), "KekStarValue");
}
diff --git a/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts b/src/Playwright.TestingHarnessTest/tests/xunit.spec.ts
similarity index 98%
rename from src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts
rename to src/Playwright.TestingHarnessTest/tests/xunit.spec.ts
index e6c603c973..c10ba88ed5 100644
--- a/src/Playwright.TestingHarnessTest/tests/xunit/basic.spec.ts
+++ b/src/Playwright.TestingHarnessTest/tests/xunit.spec.ts
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-import { test, expect } from '../baseTest';
+import { test, expect } from './baseTest';
test.use({ testMode: 'xunit' });
@@ -251,7 +251,7 @@ test('should be able to parse BrowserName and LaunchOptions.Headless from runset
expect(result.stdout).not.toContain("Headless")
});
-test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer }) => {
+test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -275,7 +275,7 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
public async Task Test()
{
output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
- await Page.GotoAsync("http://example.com");
+ await Page.GotoAsync("${server.EMPTY_PAGE}");
}
}`,
'.runsettings': `
@@ -301,8 +301,8 @@ test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ ru
expect(result.stdout).not.toContain("Headless");
- const { url, auth } = proxyServer.requests.find(r => r.url === 'http://example.com/')!;;
- expect(url).toBe('http://example.com/');
+ const { url, auth } = proxyServer.requests.find(r => r.url === server.EMPTY_PAGE)!;
+ expect(url).toBe(server.EMPTY_PAGE);
expect(auth).toBe('user:pwd');
});
@@ -349,7 +349,7 @@ test('should be able to parse LaunchOptions.Args from runsettings', async ({ run
expect(result.stdout).toContain("User-Agent: hello")
});
-test('should be able to override context options', async ({ runTest }) => {
+test('should be able to override context options', async ({ runTest, server }) => {
const result = await runTest({
'ExampleTests.cs': `
using System;
@@ -376,7 +376,7 @@ test('should be able to override context options', async ({ runTest }) => {
Assert.Equal("Foobar", await Page.EvaluateAsync("() => navigator.userAgent"));
- var response = await Page.GotoAsync("https://example.com/");
+ var response = await Page.GotoAsync("${server.EMPTY_PAGE}");
Assert.Equal("KekStarValue", await response.Request.HeaderValueAsync("Kekstar"));
}
diff --git a/src/Playwright.TestingHarnessTest/tests/xunit.v3.spec.ts b/src/Playwright.TestingHarnessTest/tests/xunit.v3.spec.ts
new file mode 100644
index 0000000000..b278231582
--- /dev/null
+++ b/src/Playwright.TestingHarnessTest/tests/xunit.v3.spec.ts
@@ -0,0 +1,574 @@
+/*
+ * 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.
+ */
+
+import { test, expect } from './baseTest';
+
+test.use({ testMode: 'xunit.v3' });
+
+test('should be able to forward DEBUG=pw:api env var', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ try
+ {
+ await Page.Locator("button").ClickAsync(new() { Timeout = 1_000 });
+ }
+ catch
+ {
+ }
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ pw:api
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ // TODO: change back to .stdout so the stdout is correlated with the test
+ expect(result.rawStdout).toContain("pw:api")
+ expect(result.rawStdout).toContain("element is not enabled")
+ expect(result.rawStdout).toContain("retrying click action")
+});
+
+test('should be able to set the browser via the runsettings file', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ output.WriteLine("BrowserName: " + BrowserName);
+ output.WriteLine("BrowserType: " + BrowserType.Name);
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ webkit
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: webkit")
+ expect(result.stdout).toContain("BrowserType: webkit")
+ expect(/User-Agent: .*WebKit.*/.test(result.stdout)).toBeTruthy()
+});
+
+test('should prioritize browser from env over the runsettings file', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ output.WriteLine("BrowserName: " + BrowserName);
+ output.WriteLine("BrowserType: " + BrowserType.Name);
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ webkit
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings', {
+ BROWSER: 'firefox'
+ });
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: firefox")
+ expect(result.stdout).toContain("BrowserType: firefox")
+ expect(/User-Agent: .*Firefox.*/.test(result.stdout)).toBeTruthy()
+});
+
+test('should be able to make the browser headed via the env', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ output.WriteLine("BrowserName: " + BrowserName);
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ }, 'dotnet test', {
+ HEADED: '1'
+ });
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: chromium")
+ expect(result.stdout).not.toContain("Headless")
+});
+
+test('should be able to parse BrowserName and LaunchOptions.Headless from runsettings', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ output.WriteLine("BrowserName: " + BrowserName);
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ false
+
+
+ firefox
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("BrowserName: firefox")
+ expect(result.stdout).not.toContain("Headless")
+});
+
+test('should be able to parse LaunchOptions.Proxy from runsettings', async ({ runTest, proxyServer, server }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ await Page.GotoAsync("${server.EMPTY_PAGE}");
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ chromium
+
+ false
+
+ ${proxyServer.listenAddr()}
+ user
+ pwd
+
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+
+ expect(result.stdout).not.toContain("Headless");
+
+ const { url, auth } = proxyServer.requests.find(r => r.url === server.EMPTY_PAGE)!;
+ expect(url).toBe(server.EMPTY_PAGE);
+ expect(auth).toBe('user:pwd');
+});
+
+test('should be able to parse LaunchOptions.Args from runsettings', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ private readonly ITestOutputHelper output;
+
+ public (ITestOutputHelper output)
+ {
+ this.output = output;
+ }
+
+ [Fact]
+ public async Task Test()
+ {
+ output.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ ['--user-agent=hello']
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).toContain("User-Agent: hello")
+});
+
+test('should be able to override context options', async ({ runTest, server }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+
+ Assert.False(await Page.EvaluateAsync("() => matchMedia('(prefers-color-scheme: light)').matches"));
+ Assert.True(await Page.EvaluateAsync("() => matchMedia('(prefers-color-scheme: dark)').matches"));
+
+ Assert.Equal(1920, await Page.EvaluateAsync("() => window.innerWidth"));
+ Assert.Equal(1080, await Page.EvaluateAsync("() => window.innerHeight"));
+
+ Assert.Equal("Foobar", await Page.EvaluateAsync("() => navigator.userAgent"));
+
+ var response = await Page.GotoAsync("${server.EMPTY_PAGE}");
+ Assert.Equal("KekStarValue", await response.Request.HeaderValueAsync("Kekstar"));
+ }
+
+ public override BrowserNewContextOptions ContextOptions()
+ {
+ return new BrowserNewContextOptions()
+ {
+ ColorScheme = ColorScheme.Dark,
+ UserAgent = "Foobar",
+ ViewportSize = new()
+ {
+ Width = 1920,
+ Height = 1080
+ },
+ ExtraHTTPHeaders = new Dictionary {
+ { "Kekstar", "KekStarValue" }
+ }
+ };
+ }
+ }`}, 'dotnet test');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+});
+
+test('should be able to override launch options', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ Console.WriteLine("User-Agent: " + await Page.EvaluateAsync("() => navigator.userAgent"));
+ }
+ }`,
+ '.runsettings': `
+
+
+
+
+ false
+
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ expect(result.stdout).not.toContain("Headless");
+});
+
+test.describe('Expect() timeout', () => {
+ test('should have 5 seconds by default', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text");
+ }
+ }`,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 5000ms")
+ });
+
+ test('should be able to override it via each Expect() call', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text", new() { Timeout = 100 });
+ }
+ }`,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ 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({
+ 'ExampleTests.cs': `
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ await Page.SetContentAsync("");
+ await Expect(Page.Locator("button")).ToHaveTextAsync("noooo-wrong-text");
+ }
+ }`,
+ '.runsettings': `
+
+
+
+ 123
+
+
+ `,
+ }, 'dotnet test --settings=.runsettings');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain("Expect \"ToHaveTextAsync\" with timeout 123ms")
+ });
+});
+
+test.describe('ConnectOptions', () => {
+ const ExampleTestWithConnectOptions = `
+ using System;
+ using System.Threading.Tasks;
+ using Microsoft.Playwright;
+ using Microsoft.Playwright.Xunit.v3;
+ using Xunit;
+
+ namespace Playwright.TestingHarnessTest.Xunit;
+
+ public class : PageTest
+ {
+ [Fact]
+ public async Task Test()
+ {
+ await Page.GotoAsync("about:blank");
+ }
+ public override async Task<(string, BrowserTypeConnectOptions)?> ConnectOptionsAsync()
+ {
+ return ("http://127.0.0.1:1234", null);
+ }
+ }`;
+
+ test('should fail when the server is not reachable', async ({ runTest }) => {
+ const result = await runTest({
+ 'ExampleTests.cs': ExampleTestWithConnectOptions,
+ }, 'dotnet test');
+ expect(result.passed).toBe(0);
+ expect(result.failed).toBe(1);
+ expect(result.total).toBe(1);
+ expect(result.rawStdout).toContain('connect ECONNREFUSED 127.0.0.1:1234')
+ });
+
+ test('should pass when the server is reachable', async ({ runTest, launchServer }) => {
+ await launchServer({ port: 1234 });
+ const result = await runTest({
+ 'ExampleTests.cs': ExampleTestWithConnectOptions,
+ }, 'dotnet test');
+ expect(result.passed).toBe(1);
+ expect(result.failed).toBe(0);
+ expect(result.total).toBe(1);
+ });
+});
diff --git a/src/Playwright.Tests/ChromiumLauncherTests.cs b/src/Playwright.Tests/ChromiumLauncherTests.cs
deleted file mode 100644
index 30c59c183c..0000000000
--- a/src/Playwright.Tests/ChromiumLauncherTests.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-namespace Microsoft.Playwright.Tests.Firefox;
-
-public class ChromiumLauncherTests : PlaywrightTestEx
-{
- [PlaywrightTest("chromium/launcher.spec.ts", "should return background pages")]
- [Skip(SkipAttribute.Targets.Webkit, SkipAttribute.Targets.Firefox)]
- public async Task ShouldReturnBackgroundPages()
- {
- using var userDataDir = new TempDirectory();
- var extensionPath = TestUtils.GetAsset("simple-extension");
- var extensionOptions = new BrowserTypeLaunchPersistentContextOptions
- {
- Headless = false,
- Args = new[] {
- $"--disable-extensions-except={extensionPath}",
- $"--load-extension={extensionPath}",
- },
- };
- var context = await BrowserType.LaunchPersistentContextAsync(userDataDir.Path, extensionOptions);
- var backgroundPages = context.BackgroundPages;
- var backgroundPage = backgroundPages.Count > 0
- ? backgroundPages[0]
- : await WaitForBackgroundPage(context);
- Assert.NotNull(backgroundPage);
- Assert.Contains(backgroundPage, context.BackgroundPages.ToList());
- Assert.False(context.Pages.Contains(backgroundPage));
- await context.CloseAsync();
- Assert.IsEmpty(context.Pages);
- Assert.IsEmpty(context.BackgroundPages);
- }
-
- [PlaywrightTest("chromium/launcher.spec.ts", "should return background pages when recording video")]
- [Skip(SkipAttribute.Targets.Webkit, SkipAttribute.Targets.Firefox)]
- public async Task ShouldReturnBackgroundPagesWhenRecordingVideo()
- {
- using var tempDirectory = new TempDirectory();
- using var userDataDir = new TempDirectory();
- var extensionPath = TestUtils.GetAsset("simple-extension");
- var extensionOptions = new BrowserTypeLaunchPersistentContextOptions
- {
- Headless = false,
- Args = new[] {
- $"--disable-extensions-except={extensionPath}",
- $"--load-extension={extensionPath}",
- },
- RecordVideoDir = tempDirectory.Path,
- };
- var context = await BrowserType.LaunchPersistentContextAsync(userDataDir.Path, extensionOptions);
- var backgroundPages = context.BackgroundPages;
-
- var backgroundPage = backgroundPages.Count > 0
- ? backgroundPages[0]
- : await WaitForBackgroundPage(context);
- Assert.NotNull(backgroundPage);
- Assert.Contains(backgroundPage, context.BackgroundPages.ToList());
- Assert.False(context.Pages.Contains(backgroundPage));
- await context.CloseAsync();
- }
-
- private async Task WaitForBackgroundPage(IBrowserContext context)
- {
- var tsc = new TaskCompletionSource();
- context.BackgroundPage += (_, e) => tsc.TrySetResult(e);
- return await tsc.Task;
- }
-}
diff --git a/src/Playwright.Tests/InterceptionTests.cs b/src/Playwright.Tests/InterceptionTests.cs
index 8bd629f16a..14cb129bf7 100644
--- a/src/Playwright.Tests/InterceptionTests.cs
+++ b/src/Playwright.Tests/InterceptionTests.cs
@@ -88,6 +88,13 @@ bool URLMatches(string baseURL, string url, string glob)
Assert.True(URLMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
Assert.True(URLMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
+ // Case insensitive matching
+ Assert.True(URLMatches(null, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
+ Assert.True(URLMatches("http://ignored", "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
+ // Path and search query are case-sensitive
+ Assert.False(URLMatches(null, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR"));
+ Assert.False(URLMatches(null, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B"));
+
// This is not supported, we treat ? as a query separator.
Assert.That("http://localhost:8080/Simple/path.js", Does.Not.Match(GlobToRegex("http://localhost:8080/?imple/path.js")));
Assert.False(URLMatches(null, "http://playwright.dev/", "http://playwright.?ev"));
diff --git a/src/Playwright.Tests/Playwright.Tests.csproj b/src/Playwright.Tests/Playwright.Tests.csproj
index e606aa57fb..e07f8ca366 100644
--- a/src/Playwright.Tests/Playwright.Tests.csproj
+++ b/src/Playwright.Tests/Playwright.Tests.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/src/Playwright.Tests/assets/simple-extension/content-script.js b/src/Playwright.Tests/assets/simple-extension/content-script.js
deleted file mode 100644
index 965f99fd3d..0000000000
--- a/src/Playwright.Tests/assets/simple-extension/content-script.js
+++ /dev/null
@@ -1,3 +0,0 @@
-console.log('hey from the content-script');
-self.thisIsTheContentScript = true;
-
diff --git a/src/Playwright.Tests/assets/simple-extension/index.js b/src/Playwright.Tests/assets/simple-extension/index.js
deleted file mode 100644
index a0bb3f4eae..0000000000
--- a/src/Playwright.Tests/assets/simple-extension/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Mock script for background extension
-window.MAGIC = 42;
diff --git a/src/Playwright.Tests/assets/simple-extension/manifest.json b/src/Playwright.Tests/assets/simple-extension/manifest.json
deleted file mode 100644
index da2cd082ed..0000000000
--- a/src/Playwright.Tests/assets/simple-extension/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "Simple extension",
- "version": "0.1",
- "background": {
- "scripts": ["index.js"]
- },
- "content_scripts": [{
- "matches": [""],
- "css": [],
- "js": ["content-script.js"]
- }],
- "permissions": ["background", "activeTab"],
- "manifest_version": 2
-}
diff --git a/src/Playwright.Xunit.v3/BrowserService.cs b/src/Playwright.Xunit.v3/BrowserService.cs
new file mode 100644
index 0000000000..53be2802e3
--- /dev/null
+++ b/src/Playwright.Xunit.v3/BrowserService.cs
@@ -0,0 +1,100 @@
+/*
+ * 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;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.Playwright.TestAdapter;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+internal class BrowserService : IWorkerService
+{
+ public IBrowser Browser { get; private set; }
+
+ private BrowserService(IBrowser browser)
+ {
+ Browser = browser;
+ }
+
+ public static Task Register(WorkerAwareTest test, IBrowserType browserType, (string, BrowserTypeConnectOptions?)? connectOptions)
+ {
+ return test.RegisterService("Browser", async () => new BrowserService(await CreateBrowser(browserType, connectOptions).ConfigureAwait(false)));
+ }
+
+ private static async Task CreateBrowser(IBrowserType browserType, (string WSEndpoint, BrowserTypeConnectOptions? Options)? connectOptions)
+ {
+ if (connectOptions.HasValue && connectOptions.Value.WSEndpoint != null)
+ {
+ var options = new BrowserTypeConnectOptions(connectOptions?.Options ?? new());
+ var headers = options.Headers?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? [];
+ headers.Add("x-playwright-launch-options", JsonSerializer.Serialize(PlaywrightSettingsProvider.LaunchOptions, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }));
+ options.Headers = headers;
+ return await browserType.ConnectAsync(connectOptions!.Value.WSEndpoint, options).ConfigureAwait(false);
+ }
+
+ var legacyBrowser = await ConnectBasedOnEnv(browserType);
+ if (legacyBrowser != null)
+ {
+ return legacyBrowser;
+ }
+ return await browserType.LaunchAsync(PlaywrightSettingsProvider.LaunchOptions).ConfigureAwait(false);
+ }
+
+ // TODO: Remove at some point
+ private static async Task ConnectBasedOnEnv(IBrowserType browserType)
+ {
+ var accessToken = Environment.GetEnvironmentVariable("PLAYWRIGHT_SERVICE_ACCESS_TOKEN");
+ var serviceUrl = Environment.GetEnvironmentVariable("PLAYWRIGHT_SERVICE_URL");
+
+ if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(serviceUrl))
+ {
+ return null;
+ }
+
+ var exposeNetwork = Environment.GetEnvironmentVariable("PLAYWRIGHT_SERVICE_EXPOSE_NETWORK") ?? "";
+ var os = Uri.EscapeDataString(Environment.GetEnvironmentVariable("PLAYWRIGHT_SERVICE_OS") ?? "linux");
+ var runId = Uri.EscapeDataString(Environment.GetEnvironmentVariable("PLAYWRIGHT_SERVICE_RUN_ID") ?? DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture));
+ var apiVersion = "2023-10-01-preview";
+ var wsEndpoint = $"{serviceUrl}?os={os}&runId={runId}&api-version={apiVersion}";
+
+ return await browserType.ConnectAsync(wsEndpoint, new BrowserTypeConnectOptions
+ {
+ Timeout = 3 * 60 * 1000,
+ ExposeNetwork = exposeNetwork,
+ Headers = new Dictionary
+ {
+ ["Authorization"] = $"Bearer {accessToken}",
+ ["x-playwright-launch-options"] = JsonSerializer.Serialize(PlaywrightSettingsProvider.LaunchOptions, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull })
+ }
+ }).ConfigureAwait(false);
+ }
+
+ public Task ResetAsync() => Task.CompletedTask;
+ public Task DisposeAsync() => Browser.CloseAsync();
+}
diff --git a/src/Playwright.Xunit.v3/BrowserTest.cs b/src/Playwright.Xunit.v3/BrowserTest.cs
new file mode 100644
index 0000000000..1529f8ce6e
--- /dev/null
+++ b/src/Playwright.Xunit.v3/BrowserTest.cs
@@ -0,0 +1,64 @@
+/*
+ * 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.Threading.Tasks;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+public class BrowserTest : PlaywrightTest
+{
+ public IBrowser Browser { get; internal set; } = null!;
+ private readonly List _contexts = new();
+
+ public async Task NewContext(BrowserNewContextOptions? options = null)
+ {
+ var context = await Browser.NewContextAsync(options).ConfigureAwait(false);
+ _contexts.Add(context);
+ return context;
+ }
+
+ public override async ValueTask InitializeAsync()
+ {
+ await base.InitializeAsync().ConfigureAwait(false);
+ var service = await BrowserService.Register(this, BrowserType, await ConnectOptionsAsync()).ConfigureAwait(false);
+ Browser = service.Browser;
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ if (TestOk())
+ {
+ foreach (var context in _contexts)
+ {
+ await context.CloseAsync().ConfigureAwait(false);
+ }
+ }
+ _contexts.Clear();
+ Browser = null!;
+ await base.DisposeAsync().ConfigureAwait(false);
+ }
+
+ public virtual Task<(string, BrowserTypeConnectOptions?)?> ConnectOptionsAsync() => Task.FromResult<(string, BrowserTypeConnectOptions?)?>(null);
+}
diff --git a/src/Playwright.Xunit.v3/ContextTest.cs b/src/Playwright.Xunit.v3/ContextTest.cs
new file mode 100644
index 0000000000..76622275e0
--- /dev/null
+++ b/src/Playwright.Xunit.v3/ContextTest.cs
@@ -0,0 +1,47 @@
+/*
+ * 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.Threading.Tasks;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+public class ContextTest : BrowserTest
+{
+ public IBrowserContext Context { get; private set; } = null!;
+
+ public override async ValueTask InitializeAsync()
+ {
+ await base.InitializeAsync().ConfigureAwait(false);
+ Context = await NewContext(ContextOptions()).ConfigureAwait(false);
+ }
+
+ public virtual BrowserNewContextOptions ContextOptions()
+ {
+ return new()
+ {
+ Locale = "en-US",
+ ColorScheme = ColorScheme.Light,
+ };
+ }
+}
diff --git a/src/Playwright.Xunit.v3/PageTest.cs b/src/Playwright.Xunit.v3/PageTest.cs
new file mode 100644
index 0000000000..abe0df2c7d
--- /dev/null
+++ b/src/Playwright.Xunit.v3/PageTest.cs
@@ -0,0 +1,38 @@
+/*
+ * 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.Threading.Tasks;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+public class PageTest : ContextTest
+{
+ public IPage Page { get; private set; } = null!;
+
+ public override async ValueTask InitializeAsync()
+ {
+ await base.InitializeAsync().ConfigureAwait(false);
+ Page = await Context.NewPageAsync().ConfigureAwait(false);
+ }
+}
diff --git a/src/Playwright.Xunit.v3/Playwright.Xunit.v3.csproj b/src/Playwright.Xunit.v3/Playwright.Xunit.v3.csproj
new file mode 100644
index 0000000000..eb293814da
--- /dev/null
+++ b/src/Playwright.Xunit.v3/Playwright.Xunit.v3.csproj
@@ -0,0 +1,45 @@
+
+
+
+ Microsoft.Playwright.Xunit.v3
+ Microsoft.Playwright.Xunit.v3
+ A set of helpers and fixtures to enable using Playwright in xUnit tests.
+
+ Playwright enables reliable end-to-end testing for modern web apps. This package brings in additional helpers
+ and fixtures to enable using it within xUnit.
+
+ icon.png
+ netstandard2.0
+ true
+ true
+ Microsoft.Playwright.Xunit.v3
+ 0.0.0
+ True
+ Microsoft.Playwright.Xunit.v3
+ ./nupkg
+ true
+ enable
+ README.md
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/src/Playwright.Xunit.v3/PlaywrightTest.cs b/src/Playwright.Xunit.v3/PlaywrightTest.cs
new file mode 100644
index 0000000000..0c99831ca4
--- /dev/null
+++ b/src/Playwright.Xunit.v3/PlaywrightTest.cs
@@ -0,0 +1,55 @@
+/*
+ * 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.Threading.Tasks;
+using Microsoft.Playwright.TestAdapter;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+public class PlaywrightTest : WorkerAwareTest
+{
+ public string BrowserName { get; internal set; } = null!;
+
+ private static readonly Task _playwrightTask = Microsoft.Playwright.Playwright.CreateAsync();
+
+ public IPlaywright Playwright { get; private set; } = null!;
+ public IBrowserType BrowserType { get; private set; } = null!;
+
+ public override async ValueTask InitializeAsync()
+ {
+ await base.InitializeAsync().ConfigureAwait(false);
+ Playwright = await _playwrightTask.ConfigureAwait(false);
+ BrowserName = PlaywrightSettingsProvider.BrowserName;
+ BrowserType = Playwright[BrowserName];
+ Playwright.Selectors.SetTestIdAttribute("data-testid");
+ }
+
+ public static void SetDefaultExpectTimeout(float timeout) => Assertions.SetDefaultExpectTimeout(timeout);
+
+ public ILocatorAssertions Expect(ILocator locator) => Assertions.Expect(locator);
+
+ public IPageAssertions Expect(IPage page) => Assertions.Expect(page);
+
+ public IAPIResponseAssertions Expect(IAPIResponse response) => Assertions.Expect(response);
+}
diff --git a/src/Playwright.Xunit.v3/WorkerAwareTest.cs b/src/Playwright.Xunit.v3/WorkerAwareTest.cs
new file mode 100644
index 0000000000..e07b625399
--- /dev/null
+++ b/src/Playwright.Xunit.v3/WorkerAwareTest.cs
@@ -0,0 +1,116 @@
+/*
+ * 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;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Playwright.Core;
+using Microsoft.Playwright.TestAdapter;
+using Xunit;
+
+namespace Microsoft.Playwright.Xunit.v3;
+
+public class WorkerAwareTest : IAsyncLifetime
+{
+ private static readonly ConcurrentStack _allWorkers = new();
+ private Worker _currentWorker = null!;
+
+ public WorkerAwareTest()
+ {
+ PlaywrightSettingsProvider.LoadViaEnvIfNeeded();
+ }
+
+ internal class Worker
+ {
+ private static int _lastWorkedIndex = 0;
+ public int WorkerIndex = Interlocked.Increment(ref _lastWorkedIndex);
+ public Dictionary Services = [];
+ }
+
+ public int WorkerIndex { get; internal set; }
+
+ public async Task RegisterService(string name, Func> factory) where T : class, IWorkerService
+ {
+ if (!_currentWorker.Services.ContainsKey(name))
+ {
+ _currentWorker.Services[name] = await factory().ConfigureAwait(false);
+ }
+
+ return (_currentWorker.Services[name] as T)!;
+ }
+
+ public virtual ValueTask InitializeAsync()
+ {
+ if (!_allWorkers.TryPop(out _currentWorker!))
+ {
+ _currentWorker = new();
+ }
+ WorkerIndex = _currentWorker.WorkerIndex;
+ if (PlaywrightSettingsProvider.ExpectTimeout.HasValue)
+ {
+ AssertionsBase.SetDefaultTimeout(PlaywrightSettingsProvider.ExpectTimeout.Value);
+ }
+ return new ValueTask();
+ }
+
+ public async virtual ValueTask DisposeAsync()
+ {
+ if (TestOk())
+ {
+ foreach (var kv in _currentWorker.Services)
+ {
+ await kv.Value.ResetAsync().ConfigureAwait(false);
+ }
+ _allWorkers.Push(_currentWorker);
+ }
+ else
+ {
+ foreach (var kv in _currentWorker.Services)
+ {
+ await kv.Value.DisposeAsync().ConfigureAwait(false);
+ }
+ _currentWorker.Services.Clear();
+ }
+ }
+
+ protected bool TestOk()
+ {
+ // Test is still running.
+ if (TestContext.Current.TestState == null)
+ {
+ return false;
+ }
+ return
+ TestContext.Current.TestState.Result == TestResult.Passed ||
+ TestContext.Current.TestState.Result == TestResult.Skipped;
+ }
+}
+
+public interface IWorkerService
+{
+ public Task ResetAsync();
+ public Task DisposeAsync();
+}
diff --git a/src/Playwright.Xunit/Playwright.Xunit.csproj b/src/Playwright.Xunit/Playwright.Xunit.csproj
index a2896fe895..4dcf4f239e 100644
--- a/src/Playwright.Xunit/Playwright.Xunit.csproj
+++ b/src/Playwright.Xunit/Playwright.Xunit.csproj
@@ -19,6 +19,7 @@
./nupkgtrueenable
+ README.mdtrue1.52.0
@@ -41,5 +42,6 @@
+
diff --git a/src/Playwright.sln b/src/Playwright.sln
index a8da083c63..2c6dabd1c9 100644
--- a/src/Playwright.sln
+++ b/src/Playwright.sln
@@ -1,4 +1,5 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31516.353
MinimumVisualStudioVersion = 10.0.40219.1
@@ -34,6 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playwright.TestingHarnessTe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playwright.Xunit", "Playwright.Xunit\Playwright.Xunit.csproj", "{7E427229-793C-44E5-B90E-FB8E322066FA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playwright.Xunit.v3", "Playwright.Xunit.v3\Playwright.Xunit.v3.csproj", "{6CFF37A8-7C6B-4203-9BF7-3A83752653E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playwright.MSTest.v4", "Playwright.MSTest.v4\Playwright.MSTest.v4.csproj", "{391F8C08-8C76-4D26-BBCF-8A3E00252075}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -80,6 +85,14 @@ Global
{7E427229-793C-44E5-B90E-FB8E322066FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E427229-793C-44E5-B90E-FB8E322066FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E427229-793C-44E5-B90E-FB8E322066FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CFF37A8-7C6B-4203-9BF7-3A83752653E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CFF37A8-7C6B-4203-9BF7-3A83752653E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CFF37A8-7C6B-4203-9BF7-3A83752653E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CFF37A8-7C6B-4203-9BF7-3A83752653E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {391F8C08-8C76-4D26-BBCF-8A3E00252075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {391F8C08-8C76-4D26-BBCF-8A3E00252075}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {391F8C08-8C76-4D26-BBCF-8A3E00252075}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {391F8C08-8C76-4D26-BBCF-8A3E00252075}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Playwright/API/Generated/IBrowserType.cs b/src/Playwright/API/Generated/IBrowserType.cs
index 995fe9feff..54164dfe4e 100644
--- a/src/Playwright/API/Generated/IBrowserType.cs
+++ b/src/Playwright/API/Generated/IBrowserType.cs
@@ -174,6 +174,12 @@ public partial interface IBrowserType
/// seen at chrome://version.
/// Note that browsers do not allow launching multiple instances with the same User
/// Data Directory.
+ /// Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome
+ /// user profile is not supported. Pointing userDataDir to Chrome's main "User
+ /// Data" directory (the profile used for your regular browsing) may result in pages
+ /// not loading or the browser exiting. Create and use a separate directory (for example,
+ /// an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port
+ /// for details.
///
/// Call options
Task LaunchPersistentContextAsync(string userDataDir, BrowserTypeLaunchPersistentContextOptions? options = default);
diff --git a/src/Playwright/API/Generated/IPage.cs b/src/Playwright/API/Generated/IPage.cs
index e4e19664c7..ea9ae6fbc5 100644
--- a/src/Playwright/API/Generated/IPage.cs
+++ b/src/Playwright/API/Generated/IPage.cs
@@ -1582,7 +1582,7 @@ public partial interface IPage
///
///
/// Pauses script execution. Playwright will stop executing the script and wait for
- /// the user to either press 'Resume' button in the page overlay or to call playwright.resume()
+ /// the user to either press the 'Resume' button in the page overlay or to call playwright.resume()
/// in the DevTools console.
///
///
diff --git a/src/Playwright/API/Generated/Options/APIRequestNewContextOptions.cs b/src/Playwright/API/Generated/Options/APIRequestNewContextOptions.cs
index c4ff393442..71068b6e15 100644
--- a/src/Playwright/API/Generated/Options/APIRequestNewContextOptions.cs
+++ b/src/Playwright/API/Generated/Options/APIRequestNewContextOptions.cs
@@ -91,6 +91,12 @@ public APIRequestNewContextOptions(APIRequestNewContextOptions clone)
/// that the certificate is valid for.
///
///
+ /// Client certificate authentication is only active when at least one client certificate
+ /// is provided. If you want to reject all client certificates sent by the server, you
+ /// need to provide a client certificate with an origin that does not match any
+ /// of the domains you plan to visit.
+ ///
+ ///
/// When using WebKit on macOS, accessing localhost will not pick up client certificates.
/// You can make it work by replacing localhost with local.playwright.
///
diff --git a/src/Playwright/API/Generated/Options/BrowserNewContextOptions.cs b/src/Playwright/API/Generated/Options/BrowserNewContextOptions.cs
index 5a671f9297..da6d9e9fe8 100644
--- a/src/Playwright/API/Generated/Options/BrowserNewContextOptions.cs
+++ b/src/Playwright/API/Generated/Options/BrowserNewContextOptions.cs
@@ -131,6 +131,12 @@ public BrowserNewContextOptions(BrowserNewContextOptions clone)
/// that the certificate is valid for.
///
///
+ /// Client certificate authentication is only active when at least one client certificate
+ /// is provided. If you want to reject all client certificates sent by the server, you
+ /// need to provide a client certificate with an origin that does not match any
+ /// of the domains you plan to visit.
+ ///
+ ///
/// When using WebKit on macOS, accessing localhost will not pick up client certificates.
/// You can make it work by replacing localhost with local.playwright.
///
diff --git a/src/Playwright/API/Generated/Options/BrowserNewPageOptions.cs b/src/Playwright/API/Generated/Options/BrowserNewPageOptions.cs
index be99eb6f5e..71f9550be2 100644
--- a/src/Playwright/API/Generated/Options/BrowserNewPageOptions.cs
+++ b/src/Playwright/API/Generated/Options/BrowserNewPageOptions.cs
@@ -131,6 +131,12 @@ public BrowserNewPageOptions(BrowserNewPageOptions clone)
/// that the certificate is valid for.
///
///
+ /// Client certificate authentication is only active when at least one client certificate
+ /// is provided. If you want to reject all client certificates sent by the server, you
+ /// need to provide a client certificate with an origin that does not match any
+ /// of the domains you plan to visit.
+ ///
+ ///
/// When using WebKit on macOS, accessing localhost will not pick up client certificates.
/// You can make it work by replacing localhost with local.playwright.
///
diff --git a/src/Playwright/API/Generated/Options/BrowserTypeLaunchPersistentContextOptions.cs b/src/Playwright/API/Generated/Options/BrowserTypeLaunchPersistentContextOptions.cs
index 1b05b92571..83ff8f0828 100644
--- a/src/Playwright/API/Generated/Options/BrowserTypeLaunchPersistentContextOptions.cs
+++ b/src/Playwright/API/Generated/Options/BrowserTypeLaunchPersistentContextOptions.cs
@@ -181,6 +181,12 @@ public BrowserTypeLaunchPersistentContextOptions(BrowserTypeLaunchPersistentCont
/// that the certificate is valid for.
///
///
+ /// Client certificate authentication is only active when at least one client certificate
+ /// is provided. If you want to reject all client certificates sent by the server, you
+ /// need to provide a client certificate with an origin that does not match any
+ /// of the domains you plan to visit.
+ ///
+ ///
/// When using WebKit on macOS, accessing localhost will not pick up client certificates.
/// You can make it work by replacing localhost with local.playwright.
///
diff --git a/src/Playwright/Core/AssertionsBase.cs b/src/Playwright/Core/AssertionsBase.cs
index fdb6bea588..22ed2b791b 100644
--- a/src/Playwright/Core/AssertionsBase.cs
+++ b/src/Playwright/Core/AssertionsBase.cs
@@ -33,8 +33,10 @@
using Microsoft.Playwright.Transport.Protocol;
[assembly: InternalsVisibleTo("Microsoft.Playwright.MSTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")]
+[assembly: InternalsVisibleTo("Microsoft.Playwright.MSTest.v4, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")]
[assembly: InternalsVisibleTo("Microsoft.Playwright.NUnit, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")]
[assembly: InternalsVisibleTo("Microsoft.Playwright.Xunit, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")]
+[assembly: InternalsVisibleTo("Microsoft.Playwright.Xunit.v3, PublicKey=0024000004800000940000000602000000240000525341310004000001000100059a04ca5ca77c9b4eb2addd1afe3f8464b20ee6aefe73b8c23c0e6ca278d1a378b33382e7e18d4aa8300dd22d81f146e528d88368f73a288e5b8157da9710fe6f9fa9911fb786193f983408c5ebae0b1ba5d1d00111af2816f5db55871db03d7536f4a7a6c5152d630c1e1886b1a0fb68ba5e7f64a7f24ac372090889be2ffb")]
namespace Microsoft.Playwright.Core;
diff --git a/src/Playwright/Core/Page.cs b/src/Playwright/Core/Page.cs
index 89a853688e..7c723a0b5a 100644
--- a/src/Playwright/Core/Page.cs
+++ b/src/Playwright/Core/Page.cs
@@ -1148,10 +1148,10 @@ public Task IsVisibleAsync(string selector, PageIsVisibleOptions? options
[MethodImpl(MethodImplOptions.NoInlining)]
public async Task PauseAsync()
{
- var defaultNavigationTimeout = _timeoutSettings.DefaultNavigationTimeout;
- var defaultTimeout = _timeoutSettings.DefaultTimeout;
- _timeoutSettings.SetDefaultNavigationTimeout(0);
- _timeoutSettings.SetDefaultTimeout(0);
+ var defaultNavigationTimeout = Context._timeoutSettings.DefaultNavigationTimeout;
+ var defaultTimeout = Context._timeoutSettings.DefaultTimeout;
+ Context.SetDefaultNavigationTimeout(0);
+ Context.SetDefaultTimeout(0);
try
{
await Task.WhenAny(Context.SendMessageToServerAsync("pause"), ClosedOrCrashedTcs.Task).ConfigureAwait(false);
diff --git a/src/Playwright/Core/Selectors.cs b/src/Playwright/Core/Selectors.cs
index 506fd7c9ca..44c627d2c0 100644
--- a/src/Playwright/Core/Selectors.cs
+++ b/src/Playwright/Core/Selectors.cs
@@ -53,7 +53,7 @@ public async Task RegisterAsync(string name, SelectorsRegisterOptions? options =
{
engine["contentScript"] = options.ContentScript;
}
- foreach (var context in _contextsForSelectors)
+ foreach (var context in _contextsForSelectors.ToArray())
{
await context.SendMessageToServerAsync("registerSelectorEngine", new Dictionary
{
@@ -67,7 +67,7 @@ public void SetTestIdAttribute(string attributeName)
{
Locator.SetTestIdAttribute(attributeName);
_testIdAttributeName = attributeName;
- foreach (var context in _contextsForSelectors)
+ foreach (var context in _contextsForSelectors.ToArray())
{
context.SendMessageToServerAsync(
"setTestIdAttributeName",
diff --git a/src/Playwright/Helpers/URLMatch.cs b/src/Playwright/Helpers/URLMatch.cs
index 86f8c4dfe0..a2b07b6b46 100644
--- a/src/Playwright/Helpers/URLMatch.cs
+++ b/src/Playwright/Helpers/URLMatch.cs
@@ -90,11 +90,7 @@ internal static string ConstructURLBasedOnBaseURL(string? baseUrl, string url)
{
try
{
- if (string.IsNullOrEmpty(baseUrl))
- {
- return FixupTrailingSlash(new Uri(url, UriKind.Absolute)).ToString();
- }
- return FixupTrailingSlash(new Uri(new Uri(baseUrl), new Uri(url, UriKind.RelativeOrAbsolute))).ToString();
+ return ResolveBaseURL(baseUrl, url).Resolved;
}
catch
{
@@ -254,16 +250,45 @@ string MapToken(string original, string replacement)
return newPrefix + newSuffix;
}));
- var resolved = ConstructURLBasedOnBaseURL(baseURL, relativePath);
+ var (resolved, caseInsensitivePart) = ResolveBaseURL(baseURL, relativePath);
foreach (var kvp in tokenMap)
{
- resolved = resolved.Replace(kvp.Key, kvp.Value);
+ var normalize = caseInsensitivePart?.Contains(kvp.Key) == true;
+ resolved = resolved.Replace(kvp.Key, normalize ? kvp.Value.ToLowerInvariant() : kvp.Value);
}
match = resolved;
}
return match;
}
+ private static (string Resolved, string? CaseInsensitivePart) ResolveBaseURL(string? baseUrl, string url)
+ {
+ try
+ {
+ Uri uri;
+ if (string.IsNullOrEmpty(baseUrl))
+ {
+ uri = FixupTrailingSlash(new Uri(url, UriKind.Absolute));
+ }
+ else
+ {
+ uri = FixupTrailingSlash(new Uri(new Uri(baseUrl), new Uri(url, UriKind.RelativeOrAbsolute)));
+ }
+ var resolved = uri.ToString();
+ // Schema and domain are case-insensitive.
+ var caseInsensitivePrefix = $"{uri.Scheme}://{uri.Host}";
+ if (!uri.IsDefaultPort)
+ {
+ caseInsensitivePrefix += $":{uri.Port}";
+ }
+ return (resolved, caseInsensitivePrefix);
+ }
+ catch
+ {
+ return (url, null);
+ }
+ }
+
public bool Equals(string? globMatch, Regex? reMatch, Func? funcMatch, string? baseURL, bool isWebSocketUrl)
{
return this.re?.ToString() == reMatch?.ToString() && this.re?.Options == reMatch?.Options
diff --git a/src/Playwright/Playwright.csproj b/src/Playwright/Playwright.csproj
index 2953b25013..45dbd041b7 100644
--- a/src/Playwright/Playwright.csproj
+++ b/src/Playwright/Playwright.csproj
@@ -23,6 +23,7 @@
true1.52.0enable
+ README.md
@@ -54,6 +55,7 @@
+
diff --git a/src/Playwright/Transport/Protocol/Generated/Point.cs b/src/Playwright/Transport/Protocol/Generated/Point.cs
index ea299e7093..2366cddfc8 100644
--- a/src/Playwright/Transport/Protocol/Generated/Point.cs
+++ b/src/Playwright/Transport/Protocol/Generated/Point.cs
@@ -29,8 +29,8 @@ namespace Microsoft.Playwright.Transport.Protocol;
internal class Point
{
[JsonPropertyName("x")]
- public int X { get; set; }
+ public float X { get; set; }
[JsonPropertyName("y")]
- public int Y { get; set; }
+ public float Y { get; set; }
}
diff --git a/src/Playwright/Transport/Protocol/Generated/Rect.cs b/src/Playwright/Transport/Protocol/Generated/Rect.cs
index 22069d2178..9c618dee21 100644
--- a/src/Playwright/Transport/Protocol/Generated/Rect.cs
+++ b/src/Playwright/Transport/Protocol/Generated/Rect.cs
@@ -29,14 +29,14 @@ namespace Microsoft.Playwright.Transport.Protocol;
internal class Rect
{
[JsonPropertyName("x")]
- public int X { get; set; }
+ public float X { get; set; }
[JsonPropertyName("y")]
- public int Y { get; set; }
+ public float Y { get; set; }
[JsonPropertyName("width")]
- public int Width { get; set; }
+ public float Width { get; set; }
[JsonPropertyName("height")]
- public int Height { get; set; }
+ public float Height { get; set; }
}
diff --git a/src/Playwright/Transport/Protocol/Generated/ResourceTiming.cs b/src/Playwright/Transport/Protocol/Generated/ResourceTiming.cs
index ef23b8065d..228ce39e64 100644
--- a/src/Playwright/Transport/Protocol/Generated/ResourceTiming.cs
+++ b/src/Playwright/Transport/Protocol/Generated/ResourceTiming.cs
@@ -29,26 +29,26 @@ namespace Microsoft.Playwright.Transport.Protocol;
internal class ResourceTiming
{
[JsonPropertyName("startTime")]
- public int StartTime { get; set; }
+ public float StartTime { get; set; }
[JsonPropertyName("domainLookupStart")]
- public int DomainLookupStart { get; set; }
+ public float DomainLookupStart { get; set; }
[JsonPropertyName("domainLookupEnd")]
- public int DomainLookupEnd { get; set; }
+ public float DomainLookupEnd { get; set; }
[JsonPropertyName("connectStart")]
- public int ConnectStart { get; set; }
+ public float ConnectStart { get; set; }
[JsonPropertyName("secureConnectionStart")]
- public int SecureConnectionStart { get; set; }
+ public float SecureConnectionStart { get; set; }
[JsonPropertyName("connectEnd")]
- public int ConnectEnd { get; set; }
+ public float ConnectEnd { get; set; }
[JsonPropertyName("requestStart")]
- public int RequestStart { get; set; }
+ public float RequestStart { get; set; }
[JsonPropertyName("responseStart")]
- public int ResponseStart { get; set; }
+ public float ResponseStart { get; set; }
}
diff --git a/src/Playwright/Transport/Protocol/Generated/SecurityDetails.cs b/src/Playwright/Transport/Protocol/Generated/SecurityDetails.cs
index 67b115c1a7..c42a293f69 100644
--- a/src/Playwright/Transport/Protocol/Generated/SecurityDetails.cs
+++ b/src/Playwright/Transport/Protocol/Generated/SecurityDetails.cs
@@ -38,8 +38,8 @@ internal class SecurityDetails
public string SubjectName { get; set; } = null!;
[JsonPropertyName("validFrom")]
- public int? ValidFrom { get; set; }
+ public float ValidFrom { get; set; }
[JsonPropertyName("validTo")]
- public int? ValidTo { get; set; }
+ public float ValidTo { get; set; }
}
diff --git a/src/Playwright/Transport/Protocol/Generated/SerializedValue.cs b/src/Playwright/Transport/Protocol/Generated/SerializedValue.cs
index 65c9c3c1bb..a54cb1732f 100644
--- a/src/Playwright/Transport/Protocol/Generated/SerializedValue.cs
+++ b/src/Playwright/Transport/Protocol/Generated/SerializedValue.cs
@@ -30,7 +30,7 @@ namespace Microsoft.Playwright.Transport.Protocol;
internal class SerializedValue
{
[JsonPropertyName("n")]
- public int? N { get; set; }
+ public float N { get; set; }
[JsonPropertyName("b")]
public bool? B { get; set; }