diff --git a/.gitignore b/.gitignore
index 2a3a04a21..6d12fdc15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,6 +141,12 @@ _TeamCity*
*.coverage
*.coveragexml
+# Coverlet code coverage
+coverage.json
+coverage.cobertura.xml
+coverage.opencover.xml
+TestResults/
+
# NCrunch
_NCrunch_*
.*crunch*.local.xml
diff --git a/COVERAGE_IMPLEMENTATION.md b/COVERAGE_IMPLEMENTATION.md
new file mode 100644
index 000000000..cb4d59ea9
--- /dev/null
+++ b/COVERAGE_IMPLEMENTATION.md
@@ -0,0 +1,127 @@
+# Code Coverage Implementation Summary
+
+This document summarizes the implementation of code coverage for the XHarness project.
+
+## Changes Made
+
+### 1. Package Dependencies
+- Added `coverlet.collector` version 6.0.0 to `Directory.Packages.props`
+- Added `coverlet.msbuild` version 6.0.0 to `Directory.Packages.props`
+
+### 2. Test Project Configuration
+- Created `tests/Directory.Build.props` to automatically add Coverlet packages to all test projects
+- This eliminates the need to manually add coverage packages to each test project
+
+### 3. Coverage Configuration
+- Created `tests/coverlet.runsettings` with optimized coverage settings:
+ - Excludes test assemblies, xUnit, and Moq from coverage
+ - Excludes generated code and compiler-generated code
+ - Outputs in multiple formats (Cobertura, OpenCover, JSON)
+ - Uses SourceLink for better source mapping
+ - Skips auto-properties for cleaner reports
+
+### 4. Coverage Scripts
+- **`run-coverage.sh`** - Bash script for Linux/macOS
+- **`run-coverage.ps1`** - PowerShell script for Windows
+- Both scripts:
+ - Run all tests with coverage collection
+ - Output coverage results to `artifacts/coverage/`
+ - Support configuration and format parameters
+ - Provide instructions for generating HTML reports
+
+### 5. Documentation
+- **`docs/code-coverage.md`** - Comprehensive guide including:
+ - Quick start instructions
+ - Viewing options (HTML reports, VS Code extensions, CLI)
+ - Manual execution commands
+ - Configuration details
+ - CI/CD integration examples (Azure Pipelines, GitHub Actions)
+ - Coverage thresholds
+ - Troubleshooting guide
+- Updated `README.md` with code coverage section
+
+### 6. Git Configuration
+- Updated `.gitignore` to exclude coverage output files:
+ - `coverage.json`
+ - `coverage.cobertura.xml`
+ - `coverage.opencover.xml`
+ - `TestResults/` directory
+
+## Usage
+
+### Basic Usage
+```bash
+# macOS/Linux
+./run-coverage.sh
+
+# Windows
+.\run-coverage.ps1
+```
+
+### With Parameters
+```bash
+# Run Release build with specific format
+./run-coverage.sh Release cobertura
+```
+
+### View HTML Report
+```bash
+# Install ReportGenerator (one-time)
+dotnet tool install -g dotnet-reportgenerator-globaltool
+
+# Generate HTML report
+reportgenerator \
+ -reports:"artifacts/coverage/**/coverage.cobertura.xml" \
+ -targetdir:"artifacts/coverage/report" \
+ -reporttypes:Html
+
+# Open report
+open artifacts/coverage/report/index.html
+```
+
+## Current Coverage
+
+Initial test run shows:
+- **Line Coverage**: ~39.79%
+- **Branch Coverage**: ~39.63%
+
+This provides a baseline for tracking coverage improvements over time.
+
+## Next Steps (Optional)
+
+1. **CI Integration**: Add coverage collection to Azure Pipelines
+2. **Coverage Badge**: Add coverage badge to README
+3. **Coverage Thresholds**: Enforce minimum coverage requirements
+4. **Coverage Trends**: Track coverage changes over time
+5. **Coverage Reports**: Publish reports to Azure DevOps or Codecov
+
+## Files Modified
+
+- `Directory.Packages.props` - Added Coverlet packages
+- `.gitignore` - Added coverage output exclusions
+- `README.md` - Added code coverage section
+
+## Files Created
+
+- `tests/Directory.Build.props` - Test project configuration
+- `tests/coverlet.runsettings` - Coverage settings
+- `run-coverage.sh` - Coverage script for Unix
+- `run-coverage.ps1` - Coverage script for Windows
+- `docs/code-coverage.md` - Comprehensive documentation
+- `COVERAGE_IMPLEMENTATION.md` - This file
+
+## Testing
+
+The implementation has been tested and verified to:
+- ✅ Restore packages successfully
+- ✅ Run all test projects
+- ✅ Generate coverage data in multiple formats
+- ✅ Output results to `artifacts/coverage/`
+- ✅ Properly exclude test assemblies from coverage
+
+## Notes
+
+- Coverlet version 6.0.0 is used (latest available in dotnet-public feed)
+- Coverage data is generated per test project in separate directories
+- Multiple output formats are generated simultaneously (Cobertura, OpenCover, JSON)
+- Scripts are designed to work with XHarness's existing build infrastructure
diff --git a/COVERAGE_QUICKSTART.md b/COVERAGE_QUICKSTART.md
new file mode 100644
index 000000000..4934b264f
--- /dev/null
+++ b/COVERAGE_QUICKSTART.md
@@ -0,0 +1,95 @@
+# Quick Start: Code Coverage in XHarness
+
+## 🚀 Run Coverage (Simplest)
+
+```bash
+./run-coverage.sh
+```
+
+That's it! Coverage will be generated in `artifacts/coverage/`.
+
+---
+
+## 📊 View Coverage Report (HTML)
+
+### One-time setup:
+```bash
+dotnet tool install -g dotnet-reportgenerator-globaltool
+```
+
+### Generate and view report:
+```bash
+# Generate HTML report
+reportgenerator \
+ -reports:"artifacts/coverage/**/coverage.cobertura.xml" \
+ -targetdir:"artifacts/coverage/report" \
+ -reporttypes:Html
+
+# Open in browser (macOS)
+open artifacts/coverage/report/index.html
+
+# Or Linux
+xdg-open artifacts/coverage/report/index.html
+```
+
+---
+
+## 🎯 Quick Commands
+
+### Run with different configuration:
+```bash
+./run-coverage.sh Release
+```
+
+### Run with specific format:
+```bash
+./run-coverage.sh Debug opencover
+```
+
+### Run a single test project:
+```bash
+dotnet test tests/Microsoft.DotNet.XHarness.CLI.Tests/Microsoft.DotNet.XHarness.CLI.Tests.csproj \
+ --collect:"XPlat Code Coverage" \
+ --results-directory artifacts/coverage \
+ --settings tests/coverlet.runsettings
+```
+
+### View summary in terminal:
+```bash
+reportgenerator \
+ -reports:"artifacts/coverage/**/coverage.cobertura.xml" \
+ -reporttypes:TextSummary
+```
+
+---
+
+## 📝 What's Included
+
+- ✅ Coverlet configured for all test projects
+- ✅ Scripts for Linux/macOS and Windows
+- ✅ Multiple output formats (Cobertura, OpenCover, JSON)
+- ✅ Excludes test assemblies and auto-properties
+- ✅ Optimized settings for best performance
+- ✅ Comprehensive documentation
+
+---
+
+## 📚 More Information
+
+See [docs/code-coverage.md](docs/code-coverage.md) for:
+- Advanced usage
+- CI/CD integration
+- VS Code extensions
+- Troubleshooting
+- Configuration options
+
+---
+
+## 🎨 VS Code Integration (Optional)
+
+Install the [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension to see coverage directly in your editor:
+
+1. Install the extension
+2. Run coverage: `./run-coverage.sh`
+3. Press `Ctrl+Shift+7` (or `Cmd+Shift+7` on Mac) to toggle coverage display
+4. Coverage will be shown as colored lines in the editor gutter
diff --git a/Directory.Packages.props b/Directory.Packages.props
index eadc39680..624427b66 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -19,6 +19,9 @@
+
+
+
diff --git a/README.md b/README.md
index 346676e97..6a21bb29e 100644
--- a/README.md
+++ b/README.md
@@ -212,6 +212,20 @@ The workaround we went with lies in sharing a random string with the application
To turn this workaround on, run XHarness with `--signal-app-end` and make sure your application logs the string it reads from the env variable.
Using the `TestRunner` from this repository will automatically give you this functionality.
+## Code Coverage
+
+XHarness uses [Coverlet](https://github.com/coverlet-coverage/coverlet) for code coverage. To generate coverage reports:
+
+```bash
+# Run tests with coverage (Linux/macOS)
+./run-coverage.sh
+
+# Run tests with coverage (Windows)
+.\run-coverage.ps1
+```
+
+See [docs/code-coverage.md](docs/code-coverage.md) for detailed instructions on generating and viewing coverage reports.
+
## Contribution
We welcome contributions! Please follow the [Code of Conduct](CODE_OF_CONDUCT.md).
diff --git a/docs/azure-pipelines-coverage-example.yml b/docs/azure-pipelines-coverage-example.yml
new file mode 100644
index 000000000..193ca3ecf
--- /dev/null
+++ b/docs/azure-pipelines-coverage-example.yml
@@ -0,0 +1,41 @@
+# Example: Azure Pipelines Code Coverage Integration
+
+# Add this to your existing Azure Pipelines YAML file to enable code coverage
+
+steps:
+# ... existing build steps ...
+
+# Run tests with code coverage
+- task: DotNetCoreCLI@2
+ displayName: 'Run Tests with Coverage'
+ inputs:
+ command: 'test'
+ projects: 'XHarness.slnx'
+ arguments: '--configuration $(BuildConfiguration) --collect:"XPlat Code Coverage" --settings:tests/coverlet.runsettings --results-directory:$(Build.SourcesDirectory)/artifacts/coverage'
+ publishTestResults: true
+
+# Publish code coverage results
+- task: PublishCodeCoverageResults@1
+ displayName: 'Publish Code Coverage'
+ inputs:
+ codeCoverageTool: 'Cobertura'
+ summaryFileLocation: '$(Build.SourcesDirectory)/artifacts/coverage/**/coverage.cobertura.xml'
+ reportDirectory: '$(Build.SourcesDirectory)/artifacts/coverage/report'
+ failIfCoverageEmpty: false
+
+# (Optional) Generate HTML report using ReportGenerator
+- task: reportgenerator@5
+ displayName: 'Generate Coverage Report'
+ inputs:
+ reports: '$(Build.SourcesDirectory)/artifacts/coverage/**/coverage.cobertura.xml'
+ targetdir: '$(Build.SourcesDirectory)/artifacts/coverage/report'
+ reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
+ assemblyfilters: '-*.Tests'
+
+# (Optional) Publish coverage as build artifact
+- task: PublishBuildArtifacts@1
+ displayName: 'Publish Coverage Report'
+ inputs:
+ PathtoPublish: '$(Build.SourcesDirectory)/artifacts/coverage/report'
+ ArtifactName: 'CoverageReport'
+ publishLocation: 'Container'
diff --git a/docs/code-coverage.md b/docs/code-coverage.md
new file mode 100644
index 000000000..826ad8615
--- /dev/null
+++ b/docs/code-coverage.md
@@ -0,0 +1,210 @@
+# Code Coverage in XHarness
+
+This document explains how to generate and view code coverage reports for the XHarness project.
+
+## Prerequisites
+
+- .NET 6+ SDK
+- (Optional) [ReportGenerator](https://github.com/danielpalme/ReportGenerator) for HTML reports:
+ ```bash
+ dotnet tool install -g dotnet-reportgenerator-globaltool
+ ```
+
+## Quick Start
+
+### Running Tests with Coverage (Linux/macOS)
+
+```bash
+./run-coverage.sh [Configuration] [Format]
+```
+
+**Examples:**
+```bash
+# Run with default settings (Debug, cobertura)
+./run-coverage.sh
+
+# Run Release configuration with OpenCover format
+./run-coverage.sh Release opencover
+
+# Run Debug with JSON format
+./run-coverage.sh Debug json
+```
+
+### Running Tests with Coverage (Windows)
+
+```powershell
+.\run-coverage.ps1 -configuration [Configuration] -format [Format]
+```
+
+**Examples:**
+```powershell
+# Run with default settings (Debug, cobertura)
+.\run-coverage.ps1
+
+# Run Release configuration with OpenCover format
+.\run-coverage.ps1 -configuration Release -format opencover
+```
+
+## Coverage Output Formats
+
+Coverlet supports multiple output formats:
+- **cobertura** - Cobertura XML format (default, best for CI/CD)
+- **opencover** - OpenCover XML format
+- **json** - JSON format
+- **lcov** - LCOV format
+
+## Viewing Coverage Reports
+
+### Option 1: Generate HTML Report (Recommended)
+
+After running tests with coverage:
+
+```bash
+# Install ReportGenerator (one-time)
+dotnet tool install -g dotnet-reportgenerator-globaltool
+
+# Generate HTML report from Cobertura format
+reportgenerator \
+ -reports:"artifacts/coverage/**/coverage.cobertura.xml" \
+ -targetdir:"artifacts/coverage/report" \
+ -reporttypes:Html
+
+# Open the report
+open artifacts/coverage/report/index.html # macOS
+xdg-open artifacts/coverage/report/index.html # Linux
+start artifacts/coverage/report/index.html # Windows
+```
+
+### Option 2: Use VS Code Extensions
+
+Install one of these VS Code extensions:
+- [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters)
+- [Code Coverage](https://marketplace.visualstudio.com/items?itemName=markis.code-coverage)
+
+These extensions will show coverage directly in the editor.
+
+### Option 3: Use Command-Line Summary
+
+```bash
+# Install reportgenerator if not already installed
+dotnet tool install -g dotnet-reportgenerator-globaltool
+
+# Generate summary to console
+reportgenerator \
+ -reports:"artifacts/coverage/**/coverage.cobertura.xml" \
+ -reporttypes:TextSummary
+```
+
+## Manual Test Execution with Coverage
+
+If you prefer more control, you can run individual test projects:
+
+```bash
+# Run a specific test project with coverage
+dotnet test tests/Microsoft.DotNet.XHarness.CLI.Tests/Microsoft.DotNet.XHarness.CLI.Tests.csproj \
+ --collect:"XPlat Code Coverage" \
+ --results-directory artifacts/coverage \
+ --settings tests/coverlet.runsettings
+```
+
+## Coverage Configuration
+
+Coverage behavior is configured in `tests/coverlet.runsettings`:
+
+### Key Settings:
+
+- **Exclude**: Assemblies/types to exclude from coverage (e.g., test assemblies, third-party libraries)
+- **ExcludeByAttribute**: Exclude members with specific attributes (e.g., `[GeneratedCode]`)
+- **ExcludeByFile**: Exclude specific files or patterns
+- **SkipAutoProps**: Skip auto-implemented properties
+- **UseSourceLink**: Use SourceLink for better source file mapping
+
+### Customizing Coverage
+
+Edit `tests/coverlet.runsettings` to:
+- Add more exclusions
+- Change output formats
+- Adjust threshold values
+- Include/exclude specific assemblies
+
+Example exclusions:
+```xml
+[*.Tests]*,[xunit.*]*,[Moq]*,[System.*]*
+```
+
+## Integration with CI/CD
+
+### Azure Pipelines
+
+Add this to your pipeline YAML:
+
+```yaml
+- task: DotNetCoreCLI@2
+ displayName: 'Run Tests with Coverage'
+ inputs:
+ command: 'test'
+ projects: '**/*Tests.csproj'
+ arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" --settings:tests/coverlet.runsettings'
+
+- task: PublishCodeCoverageResults@1
+ displayName: 'Publish Coverage Results'
+ inputs:
+ codeCoverageTool: 'Cobertura'
+ summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
+```
+
+### GitHub Actions
+
+Add this to your workflow:
+
+```yaml
+- name: Run Tests with Coverage
+ run: ./run-coverage.sh Release cobertura
+
+- name: Upload Coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ files: artifacts/coverage/**/coverage.cobertura.xml
+ fail_ci_if_error: true
+```
+
+## Coverage Thresholds
+
+You can enforce minimum coverage thresholds by adding to test project `.csproj` files:
+
+```xml
+
+ cobertura
+ 80
+ line,branch,method
+ total
+
+```
+
+This will fail the build if coverage falls below the threshold.
+
+## Troubleshooting
+
+### Coverage is 0% or Empty
+
+1. Ensure test assemblies are being excluded: `[*.Tests]*`
+2. Check that source projects are referenced, not just DLLs
+3. Verify the `IncludeTestAssembly` setting is `false`
+
+### Missing Source Files in Report
+
+1. Enable SourceLink: `true`
+2. Ensure PDB files are being generated
+3. Check that source file paths are correct
+
+### Performance Issues
+
+1. Use `true` to record only first hit per line
+2. Exclude more assemblies/files
+3. Run specific test projects instead of entire solution
+
+## Additional Resources
+
+- [Coverlet Documentation](https://github.com/coverlet-coverage/coverlet)
+- [ReportGenerator Documentation](https://github.com/danielpalme/ReportGenerator)
+- [Code Coverage Best Practices](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-code-coverage)
diff --git a/run-coverage.ps1 b/run-coverage.ps1
new file mode 100644
index 000000000..2bebd6ed0
--- /dev/null
+++ b/run-coverage.ps1
@@ -0,0 +1,41 @@
+[CmdletBinding(PositionalBinding=$false)]
+Param(
+ [string]$configuration = "Debug",
+ [string]$format = "cobertura"
+)
+
+Set-StrictMode -Version 2.0
+$ErrorActionPreference = "Stop"
+
+$RepoRoot = Join-Path $PSScriptRoot ""
+$OutputDir = Join-Path $RepoRoot "artifacts\coverage"
+$RunSettings = Join-Path $RepoRoot "tests\coverlet.runsettings"
+
+Write-Host "Running tests with code coverage..." -ForegroundColor Cyan
+Write-Host "Configuration: $configuration"
+Write-Host "Output format: $format"
+Write-Host ""
+
+# Clean previous coverage results
+if (Test-Path $OutputDir) {
+ Remove-Item -Recurse -Force $OutputDir
+}
+New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
+
+# Run tests with coverage
+& dotnet test "$RepoRoot\XHarness.slnx" `
+ --configuration $configuration `
+ --collect:"XPlat Code Coverage" `
+ --results-directory $OutputDir `
+ --settings:$RunSettings `
+ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=$format
+
+Write-Host ""
+Write-Host "Coverage results generated in: $OutputDir" -ForegroundColor Green
+Write-Host ""
+Write-Host "To generate an HTML report, install ReportGenerator:"
+Write-Host " dotnet tool install -g dotnet-reportgenerator-globaltool"
+Write-Host ""
+Write-Host "Then run:"
+Write-Host " reportgenerator -reports:`"$OutputDir\**\coverage.$format.xml`" -targetdir:`"$OutputDir\report`" -reporttypes:Html"
+Write-Host ""
diff --git a/run-coverage.sh b/run-coverage.sh
new file mode 100755
index 000000000..5c8bf4c05
--- /dev/null
+++ b/run-coverage.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+source="${BASH_SOURCE[0]}"
+
+# resolve $source until the file is no longer a symlink
+while [[ -h "$source" ]]; do
+ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+ source="$(readlink "$source")"
+ # if $source was a relative symlink, we need to resolve it relative to the path where the
+ # symlink file was located
+ [[ $source != /* ]] && source="$scriptroot/$source"
+done
+scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+
+output_dir="$scriptroot/artifacts/coverage"
+configuration="${1:-Debug}"
+format="${2:-cobertura}"
+
+echo "Running tests with code coverage..."
+echo "Configuration: $configuration"
+echo "Output format: $format"
+echo ""
+
+# Clean previous coverage results
+rm -rf "$output_dir"
+mkdir -p "$output_dir"
+
+# Run tests with coverage
+dotnet test "$scriptroot/XHarness.slnx" \
+ --configuration "$configuration" \
+ --collect:"XPlat Code Coverage" \
+ --results-directory "$output_dir" \
+ --settings:"$scriptroot/tests/coverlet.runsettings" \
+ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format="$format"
+
+echo ""
+echo "Coverage results generated in: $output_dir"
+echo ""
+echo "To generate an HTML report, install ReportGenerator:"
+echo " dotnet tool install -g dotnet-reportgenerator-globaltool"
+echo ""
+echo "Then run:"
+echo " reportgenerator -reports:\"$output_dir/**/coverage.$format.xml\" -targetdir:\"$output_dir/report\" -reporttypes:Html"
+echo ""
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
new file mode 100644
index 000000000..d7ddee07d
--- /dev/null
+++ b/tests/Directory.Build.props
@@ -0,0 +1,15 @@
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings
new file mode 100644
index 000000000..44b80b6e0
--- /dev/null
+++ b/tests/coverlet.runsettings
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+ cobertura,opencover,json
+ [*.Tests]*,[xunit.*]*,[Moq]*
+ Obsolete,GeneratedCode,CompilerGenerated
+ **/Migrations/*.cs
+ ../../src/**
+ false
+ true
+ false
+ true
+ true
+
+
+
+
+
diff --git a/tests/integration-tests/Apple/Simulator.Scouting.Commands.Tests.proj b/tests/integration-tests/Apple/Simulator.Scouting.Commands.Tests.proj
index eda021197..2a2741447 100644
--- a/tests/integration-tests/Apple/Simulator.Scouting.Commands.Tests.proj
+++ b/tests/integration-tests/Apple/Simulator.Scouting.Commands.Tests.proj
@@ -2,7 +2,7 @@
-
+
@@ -10,8 +10,8 @@
Change to specify the exact Xcode version for testing.
In the CustomCommands below we can probe installed Xcode versions on Helix with `ls -al /Applications` and then choosing the write path.
-->
- Xcode_16_beta_6
- 18.0
+ Xcode_26.0.1
+ 26.0
@@ -37,6 +37,7 @@
00:03:30
-
+