Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions .github/steps/install_dependencies/action.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
name: Install Dependencies
description: ""
description: "Install the pinned .NET SDK, required host prerequisites, and Uno workloads for CI validation."

inputs:
target-platform:
description: 'The platform to install dependencies for. #See available values at https://platform.uno/docs/articles/external/uno.check/doc/using-uno-check.html'
required: false
default: 'all'
dotnet-version:
description: 'Installs and sets the .NET SDK Version'
install-windows-sdk:
description: 'Whether to install the Windows SDK ISO bootstrap step. Leave false for normal dotnet-based CI validation.'
required: false
default: '10.0.x'
default: 'false'
run-uno-check:
description: 'Whether to run uno-check and install Uno workloads. Leave false for normal dotnet-based CI validation.'
required: false
default: 'false'
sdkVersion:
description: 'The version of the Windows Sdk'
required: false
Expand All @@ -19,20 +23,21 @@ runs:
using: "composite"
steps:
# Install .NET
- name: Setup .NET ${{ inputs.dotnet-version }}
uses: actions/setup-dotnet@v3
- name: Setup .NET SDK from global.json
uses: actions/setup-dotnet@v4
with:
dotnet-version: '${{ inputs.dotnet-version }}'
global-json-file: 'global.json'

# Install Windows SDK
- name: Install Windows SDK ${{ inputs.sdkVersion }}
shell: pwsh
if: ${{ runner.os == 'Windows' }}
if: ${{ runner.os == 'Windows' && inputs.install-windows-sdk == 'true' }}
run: .\.github\Install-WindowsSdkISO.ps1 ${{ inputs.sdkVersion }}

# Run Uno.Check
- name: Install ${{ inputs.target-platform }} Workloads
shell: pwsh
if: ${{ inputs.run-uno-check == 'true' }}
run: |
dotnet tool install -g uno.check
("${{ inputs.target-platform }} ".Split(' ') | ForEach-Object {
Expand Down
136 changes: 115 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: Validation Pipeline

on:
push:
Expand All @@ -7,54 +7,148 @@ on:
- release/**

pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
- release/**
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
STEP_TIMEOUT_MINUTES: 60

jobs:
smoke_test:
name: Smoke Test (Debug Build of DotPilot)
build:
name: Build
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Dependencies
timeout-minutes: ${{ fromJSON(env.STEP_TIMEOUT_MINUTES) }}
timeout-minutes: 60
uses: "./.github/steps/install_dependencies"

# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.3.1
- name: Format
shell: pwsh
run: dotnet format DotPilot.slnx --verify-no-changes

- name: Build DotPilot (Debug)
- name: Build
shell: pwsh
run: msbuild ./DotPilot/DotPilot.csproj /r
run: dotnet build DotPilot.slnx

unit_test:
- name: Analyze
shell: pwsh
run: dotnet build DotPilot.slnx -warnaserror

unit_tests:
name: Unit Tests
runs-on: windows-latest
timeout-minutes: 60
needs:
- build
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Dependencies
timeout-minutes: ${{ fromJSON(env.STEP_TIMEOUT_MINUTES) }}
timeout-minutes: 60
uses: "./.github/steps/install_dependencies"

# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.3.1
- name: Run Unit Tests
shell: pwsh
run: dotnet test ./DotPilot.Tests/DotPilot.Tests.csproj --logger GitHubActions --blame-crash

coverage:
name: Coverage
runs-on: windows-latest
timeout-minutes: 60
needs:
- build
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Build DotPilot.Tests (Release)
- name: Install Dependencies
timeout-minutes: 60
uses: "./.github/steps/install_dependencies"

- name: Run Coverage
shell: pwsh
run: msbuild ./DotPilot.Tests/DotPilot.Tests.csproj /p:Configuration=Release /p:OverrideTargetFramework=net10.0 /r
run: dotnet test ./DotPilot.Tests/DotPilot.Tests.csproj --settings ./DotPilot.Tests/coverlet.runsettings --logger GitHubActions --blame-crash --collect:"XPlat Code Coverage"

- name: Run Unit Tests
ui_tests:
name: UI Tests
runs-on: windows-latest
timeout-minutes: 60
needs:
- build
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Dependencies
timeout-minutes: 60
uses: "./.github/steps/install_dependencies"

- name: Run UI Tests
shell: pwsh
run: dotnet test ./DotPilot.Tests/DotPilot.Tests.csproj --no-build -c Release --logger GitHubActions --blame-crash --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
run: dotnet test ./DotPilot.UITests/DotPilot.UITests.csproj --logger GitHubActions --blame-crash

desktop_artifacts:
name: Desktop Artifact (${{ matrix.name }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
needs:
- build
- unit_tests
- coverage
- ui_tests
strategy:
fail-fast: false
matrix:
include:
- name: macOS
runner: macos-latest
artifact_name: dotpilot-desktop-macos
output_path: artifacts/publish/macos
- name: Windows
runner: windows-latest
artifact_name: dotpilot-desktop-windows
output_path: artifacts/publish/windows
- name: Linux
runner: ubuntu-latest
artifact_name: dotpilot-desktop-linux
output_path: artifacts/publish/linux
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup .NET SDK from global.json
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
Comment on lines +139 to +142
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The desktop_artifacts job only runs actions/setup-dotnet but skips the shared install_dependencies composite action that the other jobs use. That action also runs uno-check to install Uno Platform workloads, which are likely required for dotnet publish with net10.0-desktop. Without workloads installed, this job may fail on CI. Consider using the shared install_dependencies action here as well, or at minimum adding a workload install step.

Copilot uses AI. Check for mistakes.

- name: Publish Desktop App
shell: pwsh
run: dotnet publish ./DotPilot/DotPilot.csproj -c Release -f net10.0-desktop -o ./${{ matrix.output_path }}

- name: Upload Desktop Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ./${{ matrix.output_path }}
if-no-files-found: error
retention-days: 14
21 changes: 16 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# AGENTS.md

Project: dotPilot
Stack: .NET 10 `Uno Platform` desktop app with central package management, `NUnit` unit tests, and `Uno.UITest` smoke coverage
Stack: .NET 10 `Uno Platform` desktop app with central package management, `NUnit` unit tests, and `Uno.UITest` browser UI coverage

Follows [MCAF](https://mcaf.managed-code.com/)

Expand Down Expand Up @@ -122,19 +122,23 @@ Skill-management rules for this `.NET` solution:
- `test`: `dotnet test DotPilot.slnx`
- `format`: `dotnet format DotPilot.slnx --verify-no-changes`
- `analyze`: `dotnet build DotPilot.slnx -warnaserror`
- `coverage`: `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --collect:"XPlat Code Coverage"`
- `coverage`: `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"`
- `publish-desktop`: `dotnet publish DotPilot/DotPilot.csproj -c Release -f net10.0-desktop`

For this app:

- unit tests currently use `NUnit` through the default `VSTest` runner
- UI smoke tests live in `DotPilot.UITests` and are a mandatory part of normal verification; the harness must provision or resolve browser-driver prerequisites automatically instead of skipping when local setup is missing
- UI tests live in `DotPilot.UITests` and are a mandatory part of normal verification; the harness must provision or resolve browser-driver prerequisites automatically instead of skipping when local setup is missing
- `format` uses `dotnet format --verify-no-changes`
- coverage uses the `coverlet.collector` integration on `DotPilot.Tests`
- coverage uses the `coverlet.collector` integration on `DotPilot.Tests` with the repo runsettings file to keep generated Uno artifacts out of the coverage path
- desktop artifact validation uses `dotnet publish DotPilot/DotPilot.csproj -c Release -f net10.0-desktop`, and the GitHub Actions validation pipeline must run in the order `build -> tests -> desktop artifacts` while uploading publish outputs for macOS, Windows, and Linux
- `LangVersion` is pinned to `latest` at the root
- the repo-root lowercase `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity
- `Directory.Build.props` owns the shared analyzer and warning policy for future projects
- `Directory.Packages.props` owns centrally managed package versions
- `global.json` pins the .NET SDK and Uno SDK version used by the app and tests
- `DotPilot/DotPilot.csproj` keeps `GenerateDocumentationFile=true` with `CS1591` suppressed so `IDE0005` stays enforceable in CI across all target frameworks without inventing command-line-only build flags
- GitHub Actions validation workflows should use a descriptive workflow name instead of the generic `CI`

### Project AGENTS Policy

Expand Down Expand Up @@ -250,7 +254,10 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
- Repository or module coverage must not decrease without an explicit written exception. Coverage after the change must stay at least at the previous baseline or improve.
- Coverage is for finding gaps, not gaming a number. Coverage numbers do not replace scenario coverage or user-flow verification.
- The task is not done until the full relevant test suite is green, not only the newly added tests.
- UI smoke tests are mandatory for this repository and must run in normal agent verification; missing local browser-driver setup is a harness bug to fix, not a reason to skip the suite.
- UI tests are mandatory for this repository and must run in normal agent verification; missing local browser-driver setup is a harness bug to fix, not a reason to skip the suite.
- GitHub Actions PR validation is mandatory for every PR and must enforce the real repo verification path so test failures are caught in CI, not only locally.
- GitHub Actions PR validation must run full automated test verification, especially the real UI suite; build-only or smoke-only checks are not an acceptable substitute for pull-request gating.
- GitHub Actions validation must also produce downloadable app artifacts for macOS, Windows, and Linux so every PR and mainline run has test results plus installable build outputs.
- For `.NET`, keep the active framework and runner model explicit so agents do not mix `TUnit`, `Microsoft.Testing.Platform`, and legacy `VSTest` assumptions.
- After changing production code, run the repo-defined quality pass: format, build, analyze, focused tests, broader tests, coverage, and any configured extra gates.

Expand All @@ -274,6 +281,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
- Never commit secrets, keys, or connection strings.
- Never skip tests to make a branch green.
- Never weaken a test or analyzer without explicit justification.
- Do not remove the `DotPilot/DotPilot.csproj` XML-doc and `CS1591` configuration unless the repo adopts full public API documentation coverage or a different documented fix for Roslyn `IDE0005`.
- Never introduce mocks, fakes, stubs, or service doubles to hide real behaviour in tests or local flows.
- Never introduce a non-SOLID design unless the exception is explicitly documented under `exception_policy`.
- Never force-push to `main`.
Expand Down Expand Up @@ -306,10 +314,13 @@ Ask first:
- Use central package management for shared test and tooling packages.
- Keep one `.NET` test framework active in the solution at a time unless a documented migration is in progress.
- Validate UI changes through runnable `DotPilot.UITests` on every relevant verification pass, instead of relying only on manual browser inspection or conditional local setup.
- Keep the UI-test execution path minimal: one normal test command should produce a real result without extra harness indirection or side-effect-heavy setup.
- Keep the main GitHub Actions workflow name descriptive and keep its job order readable: `Build`, then tests, then desktop artifact publishing.

### Dislikes

- Installing stale, non-canonical, or non-`mcaf-*` skills into the repo-local agent skill directory.
- Moving root governance out of the repository root.
- Mixing multiple `.NET` test frameworks in the active solution without a documented migration plan.
- Switching desktop Uno pages into stacked or mobile-style responsive layouts during resize work unless the user explicitly asks for a different composition; desktop pages must stay desktop-first and protect geometry through sizing constraints instead.
- Adding extra UI-test orchestration complexity when the actual goal is simply to run the tests and get an honest pass or fail result.
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
See https://aka.platform.uno/using-uno-sdk#implicit-packages for more information regarding the Implicit Packages.
-->
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="NUnit" Version="4.1.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="NUnit" Version="4.5.1" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.1" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Uno.UITest.Helpers" Version="1.1.0-dev.70" />
<PackageVersion Include="Xamarin.UITest" Version="4.3.4" />
Expand Down
2 changes: 1 addition & 1 deletion DotPilot.Tests/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Stack: `.NET 10`, `NUnit`, `FluentAssertions`, `coverlet.collector`
## Local Commands

- `test`: `dotnet test DotPilot.Tests/DotPilot.Tests.csproj`
- `coverage`: `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --collect:"XPlat Code Coverage"`
- `coverage`: `dotnet test DotPilot.Tests/DotPilot.Tests.csproj --settings DotPilot.Tests/coverlet.runsettings --collect:"XPlat Code Coverage"`
- `build`: `dotnet build DotPilot.Tests/DotPilot.Tests.csproj`

## Applicable Skills
Expand Down
2 changes: 2 additions & 0 deletions DotPilot.Tests/DotPilot.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
20 changes: 20 additions & 0 deletions DotPilot.Tests/coverlet.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage">
<Configuration>
<Include>[DotPilot]*</Include>
<Exclude>[DotPilot.Tests]*,[coverlet.*]*</Exclude>
<ExcludeByAttribute>CompilerGeneratedAttribute,GeneratedCodeAttribute,ExcludeFromCodeCoverageAttribute,ObsoleteAttribute</ExcludeByAttribute>
<ExcludeByFile>**/obj/**,**/*.g.cs,**/*.g.i.cs,**/*HotReloadInfo*.cs</ExcludeByFile>
<IncludeTestAssembly>false</IncludeTestAssembly>
<SkipAutoProps>true</SkipAutoProps>
<SingleHit>true</SingleHit>
<DeterministicReport>true</DeterministicReport>
<ExcludeAssembliesWithoutSources>MissingAny</ExcludeAssembliesWithoutSources>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
7 changes: 4 additions & 3 deletions DotPilot.UITests/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# AGENTS.md

Project: `DotPilot.UITests`
Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven smoke tests
Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven UI tests

## Purpose

- This project owns UI smoke coverage for `DotPilot` through the `Uno.UITest` harness.
- This project owns browser-driven UI coverage for `DotPilot` through the `Uno.UITest` harness.
- It is intended for app-launch and visible-flow verification once the external test prerequisites are satisfied.

## Entry Points
Expand All @@ -17,10 +17,11 @@ Stack: `.NET 10`, `NUnit`, `Uno.UITest`, browser-driven smoke tests

## Boundaries

- Keep this project focused on end-to-end or smoke-level verification only.
- Keep this project focused on end-to-end browser verification only.
- Do not add app business logic or test-only production hooks here unless they are required for stable automation.
- Treat browser-driver setup and app-launch prerequisites as part of the harness, not as assumptions inside individual tests.
- The harness must make `dotnet test DotPilot.UITests/DotPilot.UITests.csproj` runnable without manual driver-path export and must fail loudly instead of silently skipping coverage.
- Keep the harness direct and minimal; prefer the smallest deterministic setup needed to run the suite and return a real result.

## Local Commands

Expand Down
Loading
Loading