I am a big fan of Aspire. It’s the best way I know to give a project a proper onboarding experience, and it adds an extra layer of joy to building applications: everything that matters, in one view.
On the frontend, that view includes my tests. Vitest UI gives me a web interface in which I bring my tests from red to green. On the backend I get dotnet test in a terminal, and while I feel at home there, watching my tests turn green is the one thing missing from my Aspire setup. So I built it: Labcoat, a web UI for tests on Microsoft.Testing.Platform that runs on its own or as a resource in your AppHost.
A web UI for your tests gets even more useful when you run several versions of your app side by side, each with its own Aspire dashboard, which is exactly what I do in my AI software factory.

What Labcoat is
Labcoat finds the test projects in your repository, builds them and lets you browse, run and debug their tests in the browser. It’s tested with MSTest 4, NUnit 4.4+, xUnit v3 and TUnit 1, as long as the project runs on Microsoft.Testing.Platform (MTP). More on that at the end.
Two ways to run it. On its own, as a .NET tool:
dnx Labcoat # run once without installing (.NET 10 SDK)
dotnet tool install -g Labcoat # or install it
labcoat
It picks the test project in the current directory, or the nearest solution, builds it and opens http://localhost:4300.
Or, the way I use it, as a resource in the AppHost:
dotnet add MyApp.AppHost package Labcoat.Aspire.Hosting
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.MyApp_Api>("api");
builder.AddLabcoat("api-unit-tests"); // finds the test projects of the solution
builder.Build().Run();
Start the AppHost and the api-unit-tests resource gets a Labcoat UI link. The dashboard shows the suite result in the resource’s state column (✓ 12 passed, ✕ 1 failed · 20 tests), and the resource has commands to run all tests, rerun the failed ones, or toggle watch mode and coverage, without opening the UI at all.
Here it is in the Support Desk sample I use to try things out, as api-unit-tests in the tools group, right next to playwright-ui and vitest-ui:

Failures you can read
Selecting a failed test shows the test’s source with the failing line marked and the error right under it, the stack trace with clickable frames (including frames in product code), console output, and an expected/actual diff: inline for short values, line by line for multi-line strings, and structural for JSON.

Every location has an “open in editor” for VS Code, Rider or Visual Studio. The browser is where I look; the editor is still where I type.
Watch mode that knows what to rerun
Press W in the UI and Labcoat rebuilds and reruns on save. Not everything, only the affected tests:
- change a test file, and the tests in that file rerun;
- change a product file, and the tests that execute the changed code rerun, using an impact map built from coverage (that needs
Microsoft.Testing.Extensions.CodeCoverage, which theMSTestand TUnit packages include). The map grows with every coverage run, or you build it up front with Build impact map. By default it maps per test class, so a change reruns every test in the classes that use that code; - when it can’t tell, it reruns the project.

The same mapping drives Run affected, which takes the changes from git instead of from the file watcher: uncommitted changes, your branch against main, or a commit range. It shows you which tests it picked and why before it runs anything.
From a failing integration test to the trace it caused
This one is for Aspire integration tests, not unit tests: the ones that start your app with DistributedApplicationTestingBuilder and call it. When one of those fails, the assertion tells you the status code was wrong, and the reason is in a service’s logs.
Test tracing is off by default. Turn it on with the Toggle test tracing command in the dashboard (or .WithTestTracing() on the resource) and each test gets its own trace: the HTTP calls it makes carry the trace context, so the spans of the services it called join that trace. The failure then shows those services’ spans and warning/error logs right under it, with links to the full trace in the dashboard. Here the test expected Created, got BadRequest, and the API’s own warning says why:

The test side is a small package per framework: Labcoat.Testing.TUnit, .NUnit and .XUnit need no code, Labcoat.Testing.MSTest needs two hooks. The fixture that starts your app adds one line, LabcoatTracing.ConfigureAspireTesting(builder), so the services send their telemetry to the same dashboard. Only test projects that reference that package are traced, and a filter in the same syntax as the search box narrows it down to the tests you want, so your unit tests stay out of it:
builder.AddLabcoat("tests")
.WithTestTracing(filter: "cat:Integration");
Tracing only works when Labcoat runs from the AppHost, and all of it is a no-op otherwise, so it’s safe to leave in for CI.
One thing Labcoat takes care of that you’d otherwise trip over: the tests don’t inherit the Aspire run’s own variables (ASPIRE_*, DCP_*, OTEL_*), so the app your test starts doesn’t hang trying to talk to the dashboard Labcoat itself runs in.
The rest
The things you’d expect from a test UI, and a few you might not:
- Coverage per file with a source viewer, coverage of a single test, and “which tests cover this line?”.
- History that survives restarts: a run strip and duration sparkline per test, flaky-test detection, a diff between any two runs, and quarantine for the test you know is flaky.
- Run 20 times to find out whether a test is flaky before you quarantine it.
- Slow and hanging tests stand out: the run shows which test has been running longest, a test still going after 60 seconds is marked as possibly hanging, and with a timeout set Labcoat cancels that project’s run while the others carry on.
- Snapshot tests with Verify: a diff of the verified and received output, including images, and accept or reject from the UI.
- Search with
is:failed,cat:Integrationormsg:timeout, a command palette onCtrl+Kand a shortcut for everything.

What you need
Labcoat is on NuGet today. It needs the .NET 10 SDK, and the Aspire integration needs Aspire 13.4.5 or later.
Your test projects need to run on MTP 2, the version MSTest 4, NUnit3TestAdapter 6, xunit.v3.mtp-v2 and TUnit 1 are on (MSTest 3, adapter 5 and the plain xunit.v3 3.x packages are still on MTP 1). Not sure if yours do? A test project on MTP is an executable, so dotnet run --project MyApp.Tests runs its tests and prints a summary. If it doesn’t, every framework needs <OutputType>Exe</OutputType>, plus:
- MSTest:
<EnableMSTestRunner>true</EnableMSTestRunner>, or use theMSTest.Sdk; - NUnit:
<EnableNUnitRunner>true</EnableNUnitRunner>with NUnit3TestAdapter 6 or later; - xUnit v3: the
xunit.v3.mtp-v2package and<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>(on xUnit v2? It has no MTP runner, so move to v3 first); - TUnit: nothing else.
If a test project still shows no tests, Labcoat tells you per project why it found nothing.
One thing you can’t skip: on the .NET 10 SDK, dotnet test runs in VSTest mode unless the global.json at the root of your repository says otherwise, and in VSTest mode these projects fail the build. Add this and dotnet test keeps working, locally and in CI:
{
"test": { "runner": "Microsoft.Testing.Platform" }
}
Microsoft’s migration guide has the rest.
Why only MTP? Labcoat drives your tests through MTP’s server mode: it starts the test app with --server and sends it discover and run requests over JSON-RPC. That protocol is part of MTP itself, so every test project on MTP speaks it, whichever framework it uses.
Each supported framework has a sample in the repository that CI runs through the packaged tool on Linux, Windows and macOS. Test projects on older .NET versions should work when that runtime is installed, but I haven’t tested that yet.
For CI there’s labcoat --run: it runs the tests once without the UI, exports JUnit, HTML, lcov or Cobertura reports, and exits with a code your pipeline understands.
It’s MIT licensed and on GitHub. Try it out with dnx Labcoat in a repository with tests, or add it to your Aspire setup directly with Labcoat.Aspire.Hosting.