XUnitAssured.Playwright.AspNetCore
6.15.2
See the version list below for details.
dotnet add package XUnitAssured.Playwright.AspNetCore --version 6.15.2
NuGet\Install-Package XUnitAssured.Playwright.AspNetCore -Version 6.15.2
<PackageReference Include="XUnitAssured.Playwright.AspNetCore" Version="6.15.2" />
<PackageVersion Include="XUnitAssured.Playwright.AspNetCore" Version="6.15.2" />
<PackageReference Include="XUnitAssured.Playwright.AspNetCore" />
paket add XUnitAssured.Playwright.AspNetCore --version 6.15.2
#r "nuget: XUnitAssured.Playwright.AspNetCore, 6.15.2"
#:package XUnitAssured.Playwright.AspNetCore@6.15.2
#addin nuget:?package=XUnitAssured.Playwright.AspNetCore&version=6.15.2
#tool nuget:?package=XUnitAssured.Playwright.AspNetCore&version=6.15.2
XUnitAssured.Playwright.AspNetCore
Browser tests against your own ASP.NET Core API, with dotnet test alone. BrowserAppFixture<TProgram> hosts the API on a real port with WebApplicationFactory, serves your built front-end on another, and launches the browser — no terminals to start by hand, so the tests run in CI.
Part of XUnitAssured.Net. Requires .NET 10 (WebApplicationFactory.UseKestrel arrived in Microsoft.AspNetCore.Mvc.Testing 10).
Installation
dotnet add package XUnitAssured.Playwright.AspNetCore
Then install the browser once, from the test project's output:
pwsh bin/Debug/net10.0/playwright.ps1 install chromium
Why
The usual WebApplicationFactory hosts the API in memory, where no browser can reach it. Browser tests then need the API, the front-end and the tests started separately, and they stay out of CI. Out of CI they go stale without anyone noticing: in a real consumer suite, the front-end changed how it stores the session and a browser test kept passing while provoking nothing.
Usage
using XUnitAssured.Playwright.AspNetCore;
public sealed class MyApp : BrowserAppFixture<Program>
{
// The built front-end (the folder with index.html), relative to the test output.
protected override string? AppDirectory => "../../../../my-app/dist";
protected override void ConfigureApi(IWebHostBuilder api) =>
api.UseEnvironment("E2ETesting");
}
public class SessionTests(MyApp app) : IClassFixture<MyApp>
{
[Fact]
public async Task Signs_In()
{
await using var page = await app.OpenPageAsync();
await ScenarioDsl.Given(page)
.NavigateTo("/login") // relative to app.AppUrl
.FillByLabel("Email", "user@test.com")
.ClickByRole(AriaRole.Button, "Sign in")
.WaitForSelector("[data-testid=dashboard]")
.ExecuteAsync()
.AssertUrlContains("/dashboard");
}
}
| Member | What it is |
|---|---|
ApiUrl |
The API's address, e.g. http://localhost:5199 |
AppUrl |
The front-end's address — the browser's base URL. The API's when there is no front-end |
Api |
The WebApplicationFactory: Api.Services for the API's services, Api.CreateClient() to arrange data over HTTP |
ApiPort, AppPort |
Override to fix a port. 0 (the default) picks a free one |
AppDirectory |
Override to serve a built front-end, with a fallback to index.html for client-side routes |
ConfigureApi |
Override to configure the API host before it starts |
Two origins, on purpose
The API and the front-end run on different ports, like an app and its API in production, so SameSite, the cookie Path and CORS with credentials apply as they would for real. The API must allow AppUrl as an origin. If its CORS policy reads the allowed origins from configuration, fix AppPort and set it in ConfigureApi; otherwise add a policy there:
protected override void ConfigureApi(IWebHostBuilder api) =>
api.ConfigureServices(services => services
.AddCors()
.AddTransient<IStartupFilter>(_ => new AllowApp(() => AppUrl)));
sealed class AllowApp(Func<string> origin) : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
{
app.UseCors(p => p.SetIsOriginAllowed(o => o == origin()).AllowCredentials().AllowAnyHeader().AllowAnyMethod());
next(app);
};
}
If the front-end was built with the API address baked in (VITE_API_URL=http://localhost:5199, for example), fix ApiPort to match.
Fixed ports: one fixture for every class
A fixed port can be bound once, and xUnit starts an IClassFixture per class, in parallel. With fixed ports, share one fixture through a collection — which also starts the API and the front-end once instead of once per class:
[CollectionDefinition("Browser")]
public sealed class BrowserCollection : ICollectionFixture<MyApp>;
[Collection("Browser")]
public class SessionTests(MyApp app) { /* ... */ }
Apps with a service worker (PWA)
Requests a service worker makes for the page bypass InterceptRoute, so an interception answers nothing once the worker controls the page. Block service workers in the settings:
protected override PlaywrightSettings CreateSettings()
{
var settings = base.CreateSettings();
settings.BlockServiceWorkers = true;
return settings;
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Mvc.Testing (>= 10.0.3)
- XUnitAssured.Playwright (>= 6.15.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.30.5 | 0 | 10/16/2026 |
| 6.30.1 | 0 | 10/16/2026 |
| 6.27.1 | 0 | 10/16/2026 |
| 6.27.0 | 0 | 10/16/2026 |
| 6.22.0 | 0 | 10/16/2026 |
| 6.21.3 | 0 | 10/16/2026 |
| 6.19.0 | 70 | 10/12/2026 |
| 6.18.1 | 80 | 10/12/2026 |
| 6.18.0 | 76 | 10/12/2026 |
| 6.17.0 | 78 | 10/12/2026 |
| 6.16.0 | 76 | 10/12/2026 |
| 6.15.7 | 75 | 10/12/2026 |
| 6.15.6 | 74 | 10/12/2026 |
| 6.15.5 | 73 | 10/12/2026 |
| 6.15.4 | 80 | 10/12/2026 |
| 6.15.3 | 75 | 10/12/2026 |
| 6.15.2 | 79 | 10/12/2026 |
| 6.15.1 | 86 | 10/12/2026 |
| 6.15.0 | 79 | 10/12/2026 |
| 6.14.0 | 77 | 10/12/2026 |
Maintenance: tests for the status, error numbering and status code of concurrent results. The packages behave as in 6.15.1; no code changes needed. Upgrade guide: https://github.com/andrewBezerra/xunit-assured-net/blob/main/UPGRADING.md