diff --git a/Documentation/ArcadeSdk.md b/Documentation/ArcadeSdk.md
index a5df38e66b2..0e931ff053d 100644
--- a/Documentation/ArcadeSdk.md
+++ b/Documentation/ArcadeSdk.md
@@ -1064,6 +1064,114 @@ To override the default Shared Framework version that is selected based on the t
Timeout to apply to an individual invocation of the test runner (e.g. `xunit.console.exe`) for a single configuration. Integer number of milliseconds.
+### Code Coverage Properties
+
+Arcade SDK supports code coverage collection when using the VSTest runner. To enable code coverage, set `UseVSTestRunner` to `true` and `CollectCoverage` to `true`.
+
+#### `CollectCoverage` (bool)
+
+Set to `true` to enable code coverage collection when running tests. Default is `false`.
+
+```text
+msbuild Project.UnitTests.csproj /t:Test /p:UseVSTestRunner=true /p:CollectCoverage=true
+```
+
+Or set it in your project file:
+
+```xml
+
+ true
+ true
+
+```
+
+#### `CodeCoverageFormat` (string)
+
+Specifies the output format for code coverage reports. Supported values: `cobertura`, `opencover`, `lcov`, `json`, or a combination like `cobertura,opencover`. Default is `cobertura`.
+
+The Cobertura format is supported by Azure DevOps and can be published using the `PublishCodeCoverageResults` task.
+
+#### `CodeCoverageOutputDirectory` (string)
+
+Directory where code coverage reports will be generated. Default is `$(ArtifactsTestResultsDir)coverage`.
+
+#### `CoverageDeterministic` (bool)
+
+Enable or disable generation of deterministic coverage reports. Default is `true`.
+
+#### `CoverageInclude` (string)
+
+Semicolon-separated list of assembly patterns to include in code coverage. Uses glob patterns. Default is empty (includes all assemblies).
+
+Example:
+```xml
+[MyProject]*;[MyLibrary]*
+```
+
+#### `CoverageExclude` (string)
+
+Semicolon-separated list of assembly patterns to exclude from code coverage. Uses glob patterns. Default is empty.
+
+Example:
+```xml
+[*.Tests]*;[xunit.*]*
+```
+
+#### `CoverageIncludeByFile` (string)
+
+Semicolon-separated list of file path patterns to include in code coverage. Uses glob patterns. Default is empty.
+
+#### `CoverageExcludeByFile` (string)
+
+Semicolon-separated list of file path patterns to exclude from code coverage. Uses glob patterns. Default is empty.
+
+Example:
+```xml
+**/*Designer.cs;**/Generated/*.cs
+```
+
+#### `CoverageExcludeByAttribute` (string)
+
+Semicolon-separated list of attributes to exclude from code coverage. Default is empty.
+
+Example:
+```xml
+Obsolete;GeneratedCode;CompilerGenerated
+```
+
+#### Example: Complete Code Coverage Configuration
+
+```xml
+
+
+ net8.0
+ true
+ true
+ cobertura
+ [*.Tests]*;[xunit.*]*
+ **/*Designer.cs
+
+
+
+
+
+
+```
+
+Then run tests with:
+```text
+./build.sh --test
+```
+
+Code coverage reports will be generated in `artifacts/TestResults/coverage/` directory in Cobertura format, which can be published to Azure DevOps using the `PublishCodeCoverageResults` task in your pipeline:
+
+```yaml
+- task: PublishCodeCoverageResults@2
+ inputs:
+ summaryFileLocation: '$(Build.SourcesDirectory)/artifacts/TestResults/coverage/**/coverage.cobertura.xml'
+ codecoverageTool: 'cobertura'
+```
+
### `GenerateResxSource` (bool)
When set to `true`, Arcade will generate a class source for all embedded .resx files.
diff --git a/Documentation/CodeCoverageExample.md b/Documentation/CodeCoverageExample.md
new file mode 100644
index 00000000000..6252a4181a5
--- /dev/null
+++ b/Documentation/CodeCoverageExample.md
@@ -0,0 +1,108 @@
+# Code Coverage Example for Arcade SDK
+
+This example demonstrates how to enable code coverage collection in test projects using Arcade SDK.
+
+## Prerequisites
+
+- Arcade SDK 11.0.0 or later
+- Test project using XUnit
+
+## Basic Configuration
+
+Add the following properties to your test project file:
+
+```xml
+
+
+ net8.0
+
+
+ true
+
+
+ true
+
+
+ cobertura
+
+
+```
+
+## Running Tests with Coverage
+
+Run tests using the build script:
+
+```bash
+./build.sh --test
+```
+
+Or using MSBuild directly:
+
+```bash
+dotnet build /t:Test /p:Configuration=Release
+```
+
+## Coverage Reports
+
+Code coverage reports will be generated in the `artifacts/TestResults/coverage/` directory.
+
+For Cobertura format, the report file will be named `coverage.cobertura.xml` and can be published to Azure DevOps.
+
+## Azure DevOps Integration
+
+Add the following task to your Azure Pipelines YAML to publish coverage results:
+
+```yaml
+- task: PublishCodeCoverageResults@2
+ inputs:
+ summaryFileLocation: '$(Build.SourcesDirectory)/artifacts/TestResults/coverage/**/coverage.cobertura.xml'
+ codecoverageTool: 'cobertura'
+ displayName: 'Publish Code Coverage Results'
+```
+
+## Advanced Configuration
+
+### Filtering Coverage
+
+Exclude specific assemblies or files from coverage:
+
+```xml
+
+
+ [*.Tests]*;[xunit.*]*;[Moq]*
+
+
+ **/*Designer.cs;**/Generated/*.cs
+
+
+ Obsolete;GeneratedCode;CompilerGenerated
+
+```
+
+### Multiple Output Formats
+
+Generate coverage in multiple formats:
+
+```xml
+
+ cobertura,opencover,lcov
+
+```
+
+## Troubleshooting
+
+### Coverage not collected
+
+- Ensure `UseVSTestRunner` is set to `true`
+- Verify `CollectCoverage` is set to `true`
+- Check that `coverlet.collector` package is restored (should be automatic)
+
+### Empty coverage report
+
+- Make sure tests are actually running and passing
+- Verify the test project has a reference to the code you want to cover
+
+## See Also
+
+- [Arcade SDK Documentation](../Documentation/ArcadeSdk.md)
+- [Coverlet Documentation](https://github.com/coverlet-coverage/coverlet)
diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.props
index 25f911cb753..ad035ae3786 100644
--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.props
+++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.props
@@ -36,4 +36,39 @@
+
+
+
+ false
+
+
+ cobertura
+
+
+ $(ArtifactsTestResultsDir)coverage
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets
index 8ae93d3051f..756ede01728 100644
--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets
+++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets
@@ -73,6 +73,7 @@
$(TestRunnerAdditionalArguments)
$(RunArguments)
$(RunCommand)
+ $(CollectCoverage)
diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/VSTest.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/VSTest.targets
index 6aba0addf1a..9b7f84946f7 100644
--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/VSTest.targets
+++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/VSTest.targets
@@ -27,6 +27,7 @@
<_TestRunnerCommand>"$(DotNetTool)" test $(_TestAssembly) --logger:"console%3Bverbosity=normal" --logger:"trx%3BLogFileName=$(_TestResultTrxFileName)" --logger:"html%3BLogFileName=$(_TestResultHtmlFileName)" "--ResultsDirectory:$(_TestResultDirectory)" "--Framework:%(TestToRun.TargetFrameworkIdentifier),Version=%(TestToRun.TargetFrameworkVersion)"
<_TestRunnerCommand Condition="'%(TestToRun.TestRunSettingsFile)' != ''">$(_TestRunnerCommand) "--settings:%(TestToRun.TestRunSettingsFile)"
+ <_TestRunnerCommand Condition="'%(TestToRun.CollectCoverage)' == 'true'">$(_TestRunnerCommand) --collect:"XPlat Code Coverage"
<_TestRunnerCommand Condition="'$(_TestRunnerAdditionalArguments)' != ''">$(_TestRunnerCommand) $(_TestRunnerAdditionalArguments)
+
+
+
+ <_GeneratedRunSettingsFile>$(ArtifactsObjDir)$(MSBuildProjectName).runsettings
+
+
+ <_CoverageIncludeFilter Condition="'$(CoverageInclude)' != ''">$([System.String]::new('$(CoverageInclude)').Replace(';',','))
+
+
+ <_CoverageExcludeFilter Condition="'$(CoverageExclude)' != ''">$([System.String]::new('$(CoverageExclude)').Replace(';',','))
+
+
+ <_CoverageIncludeByFileFilter Condition="'$(CoverageIncludeByFile)' != ''">$([System.String]::new('$(CoverageIncludeByFile)').Replace(';',','))
+
+
+ <_CoverageExcludeByFileFilter Condition="'$(CoverageExcludeByFile)' != ''">$([System.String]::new('$(CoverageExcludeByFile)').Replace(';',','))
+
+
+ <_CoverageExcludeByAttributeFilter Condition="'$(CoverageExcludeByAttribute)' != ''">$([System.String]::new('$(CoverageExcludeByAttribute)').Replace(';',','))
+
+
+
+
+ <_RunSettingsLines Include="<?xml version="1.0" encoding="utf-8"?>" />
+ <_RunSettingsLines Include="<RunSettings>" />
+ <_RunSettingsLines Include=" <DataCollectionRunSettings>" />
+ <_RunSettingsLines Include=" <DataCollectors>" />
+ <_RunSettingsLines Include=" <DataCollector friendlyName="XPlat code coverage">" />
+ <_RunSettingsLines Include=" <Configuration>" />
+ <_RunSettingsLines Include=" <Format>$(_CodeCoverageFormat)</Format>" />
+ <_RunSettingsLines Include=" <OutputPath>$(_CodeCoverageOutputPath)</OutputPath>" />
+ <_RunSettingsLines Include=" <DeterministicReport>$(_CoverageDeterministic)</DeterministicReport>" />
+ <_RunSettingsLines Include=" <Include>$(_CoverageIncludeFilter)</Include>" Condition="'$(_CoverageIncludeFilter)' != ''" />
+ <_RunSettingsLines Include=" <Exclude>$(_CoverageExcludeFilter)</Exclude>" Condition="'$(_CoverageExcludeFilter)' != ''" />
+ <_RunSettingsLines Include=" <ExcludeByFile>$(_CoverageExcludeByFileFilter)</ExcludeByFile>" Condition="'$(_CoverageExcludeByFileFilter)' != ''" />
+ <_RunSettingsLines Include=" <IncludeTestAssembly>false</IncludeTestAssembly>" />
+ <_RunSettingsLines Include=" <ExcludeByAttribute>$(_CoverageExcludeByAttributeFilter)</ExcludeByAttribute>" Condition="'$(_CoverageExcludeByAttributeFilter)' != ''" />
+ <_RunSettingsLines Include=" <SkipAutoProps>true</SkipAutoProps>" />
+ <_RunSettingsLines Include=" <DoesNotReturnAttribute>DoesNotReturnAttribute</DoesNotReturnAttribute>" />
+ <_RunSettingsLines Include=" <SingleHit>false</SingleHit>" />
+ <_RunSettingsLines Include=" <UseSourceLink>true</UseSourceLink>" />
+ <_RunSettingsLines Include=" </Configuration>" />
+ <_RunSettingsLines Include=" </DataCollector>" />
+ <_RunSettingsLines Include=" </DataCollectors>" />
+ <_RunSettingsLines Include=" </DataCollectionRunSettings>" />
+ <_RunSettingsLines Include="</RunSettings>" />
+
+
+
+
+
+
+ $(_GeneratedRunSettingsFile)
+
+
+
+
+
+
+
$(VSTestRunSettingsFile)
+
+
+ <_CodeCoverageFormat Condition="'$(CollectCoverage)' == 'true'">$(CodeCoverageFormat)
+ <_CodeCoverageOutputPath Condition="'$(CollectCoverage)' == 'true'">$(CodeCoverageOutputDirectory)
+ <_CoverageDeterministic Condition="'$(CollectCoverage)' == 'true'">$(CoverageDeterministic)