All jobs

Add Test Coverage Reporting to ASP.NET Core CI

Yes. An agent measures your .NET solution's coverage in its sandbox, merges the test projects into one report, adds a threshold to CI and opens a pull request.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads the test setup before adding a step. In an ASP.NET Core solution like jasontaylordev/CleanArchitecture, global.json pins the .NET SDK 10 line, Directory.Build.props targets net10.0 with warnings as errors and puts build output under artifacts/, and Directory.Packages.props manages versions centrally, including coverlet.collector, NUnit, NUnit3TestAdapter and Microsoft.NET.Test.Sdk. Each of tests/Domain.UnitTests, tests/Application.UnitTests, tests/Application.FunctionalTests and tests/Infrastructure.IntegrationTests references coverlet.collector as a private asset, so the collector is installed but nothing turns it on. .github/workflows/build.yml restores, builds in Release, installs the Playwright browser for Web.AcceptanceTests, and runs dotnet test --no-build --configuration Release with no collect flag, no results directory and no artifact. The repository is also a dotnet new template, which is why the workflow carries #if comment markers, and the agent keeps every edit inside the same conditional structure.

First it measures. In the sandbox it runs dotnet test with --collect:"XPlat Code Coverage" and a --results-directory, which writes one Cobertura file per test project through the collector already referenced. A coverage.runsettings file fixes the format, excludes the test assemblies and the EF Core migrations under src/Infrastructure, and is checked in so local runs and CI agree. The acceptance tests drive a browser against a running site, so code the site executes is outside the test host the collector instruments; the agent checks what those results contain and says in the pull request whether they count toward the total or are left out.

Then it merges. dotnet tool install dotnet-reportgenerator-globaltool gives ReportGenerator, which reads the per-project Cobertura files and writes one merged Cobertura file, an HTML report and a Markdown summary. The agent reads the line coverage from the merged file and sets the threshold at that number rounded down, kept in one env variable at the top of the workflow so the next change is one line. A short step after ReportGenerator compares the merged line rate with the variable and fails the job below it.

The workflow changes are small: the collect and results flags on the existing test step, a tool restore for ReportGenerator, the merge step, the threshold step, the Markdown summary appended to the job summary, and actions/upload-artifact for the HTML report. The agent runs the whole sequence in the sandbox the way the job would, with the Playwright install the workflow already does, and opens the pull request only when the threshold step passes at the measured number.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Add coverage reporting to this .NET solution's CI. Read global.json, Directory.Build.props, Directory.Packages.props, every csproj under tests/ and .github/workflows/build.yml first. Run dotnet test with the coverlet collector and a results directory in the sandbox, add a coverage.runsettings that sets Cobertura output and excludes the test assemblies and EF Core migrations, merge the per-project results with ReportGenerator into one Cobertura file, an HTML report and a Markdown summary, and read the merged line coverage. In build.yml, add the collect and results flags to the existing test step, restore ReportGenerator as a tool, add the merge step, a step that fails the job when the merged line rate falls below a threshold kept in one env variable set to the measured number rounded down, append the Markdown summary to the job summary and upload the HTML report as an artifact. Keep every edit inside the template's existing conditional markers. Say in the pull request which test projects contribute to the number and why the acceptance tests do or do not. Open a pull request with the measured coverage and the threshold.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a pull request on its own branch touching .github/workflows/build.yml, a new coverage.runsettings at the repository root, and a .config/dotnet-tools.json if the agent chose a local tool manifest over a global install. The description states the measured line coverage, the threshold variable and its value, the per-project files that were merged, and the reason the acceptance tests are in or out of the number.

The transcript holds the dotnet test run with the collector, the ReportGenerator output with its per-assembly table, the threshold comparison, and any run that failed before the runsettings excluded the right assemblies. Your repository's Build workflow runs on the pull request, so the first coverage artifact and job summary appear on the pull request itself.

Before merging, a reviewer checks that the excluded assemblies are only tests and generated migrations, that the threshold is at the measured number and not a round aspiration, and that the collect flag did not slow the job past its budget. Raising the threshold later is one variable; a follow-up message to the same session can also ask for a per-assembly minimum on src/Application.

Run it on every new .NET service repository

A team that creates services from a template adds this run to the step that creates the repository: a POST /o/{org}/sessions with the new repository in resources and this prompt, once the Rush GitHub App covers it. The measured number becomes the starting threshold for that service, so coverage can only be lowered on purpose.

An Idempotency-Key built from the repository name makes a retried request return the run it already started. A webhook_url on the key posts run.completed with the session_url that holds the pull request, so your tooling can link it from the new repository's first issue.

The use case this job follows, with its automation and trigger: Write the missing tests.

Why run it on Rush?

The threshold is only honest if it comes from a real run on a clean checkout, with the same SDK, the same browser install and the same Release build the workflow uses. A laptop with a warm artifacts/ directory and cached NuGet packages measures something else. The sandbox restores and builds from nothing, and the number in the pull request is the number CI will see.

The sandbox also runs several solutions at once, each with its own SDK install and its own results directory, so a platform team can seed thresholds across a portfolio in one afternoon. Every run keeps the collector output and the merged report in its transcript, so when a threshold is questioned later, the measurement it came from is still there.

Frequently asked questions

My tests use NUnit. Does the collector still work?

Yes. The collector attaches to the test host rather than to the test framework, which is why coverlet.collector is referenced beside NUnit3TestAdapter in each test project. dotnet test --collect:"XPlat Code Coverage" works the same for NUnit and xUnit.

Why ReportGenerator rather than uploading each project's file?

Because four Cobertura files give four numbers and no total. ReportGenerator merges them, de-duplicates the assemblies that several test projects load, and writes the Markdown summary the job summary shows. Uploading to a coverage service can be added on top; the merged file is what a service would take.

Do the Playwright acceptance tests count?

The collector only sees code running inside the test host. If the acceptance tests start the site as a separate process and drive it through a browser, that code is not in their coverage file, and the agent says so and leaves them out of the threshold rather than letting them pull the number down.

Will it write tests to raise the number?

No. The prompt asks for measurement and a gate at the measured number. Writing tests to raise coverage is its own job and its own pull request, so the threshold change can be reviewed on its own.