Repository navigation
Extract scheduled-jobs lifecycle from MainWindowViewModel into ScheduledJobsWorkflow #1093
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: ["**"] | |
| pull_request: | |
| branches: ["**"] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| secret-scan: | |
| name: Secret Scan | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Install gitleaks | |
| run: | | |
| set -euo pipefail | |
| GITLEAKS_VERSION="8.24.3" | |
| # SHA-256 from https://github.com/gitleaks/gitleaks/releases/download/v8.24.3/gitleaks_8.24.3_checksums.txt | |
| GITLEAKS_SHA256="9991e0b2903da4c8f6122b5c3186448b927a5da4deef1fe45271c3793f4ee29c" | |
| curl -sSLo /tmp/gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | |
| echo "${GITLEAKS_SHA256} /tmp/gitleaks.tar.gz" | sha256sum --check -- | |
| tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks | |
| sudo install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks | |
| - name: Scan for secrets (gitleaks) | |
| id: gitleaks | |
| run: | | |
| set +e | |
| gitleaks detect --source . --redact --report-format sarif --report-path gitleaks.sarif | |
| exit_code=$? | |
| echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| - name: Upload gitleaks report | |
| if: ${{ always() && hashFiles('gitleaks.sarif') != '' }} | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: gitleaks-report | |
| path: gitleaks.sarif | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| - name: Upload SARIF to code scanning | |
| if: ${{ always() && hashFiles('gitleaks.sarif') != '' }} | |
| uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a | |
| with: | |
| sarif_file: gitleaks.sarif | |
| - name: Fail if secrets were detected | |
| if: ${{ steps.gitleaks.outputs.exit_code != '0' }} | |
| run: | | |
| echo "Gitleaks detected potential secrets. See uploaded SARIF results for details." | |
| exit 1 | |
| agent-instructions-sync: | |
| name: Agent Instructions Sync Check | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Assert CLAUDE.md and AGENTS.md body is identical | |
| run: | | |
| # The first line (title heading) intentionally differs: | |
| # CLAUDE.md: "# Claude Agent Instructions" | |
| # AGENTS.md: "# Codex Agent Instructions" | |
| # All other content must be identical. | |
| if ! diff <(tail -n +2 CLAUDE.md) <(tail -n +2 AGENTS.md); then | |
| echo "::error::CLAUDE.md and AGENTS.md body has diverged. They must be kept identical (except the first-line title) — update both files when making changes." | |
| exit 1 | |
| fi | |
| echo "CLAUDE.md and AGENTS.md body is identical ✓" | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Audit NuGet dependencies for vulnerabilities | |
| run: dotnet list Arbor.HttpClient.slnx package --vulnerable --include-transitive | |
| - name: Build | |
| run: dotnet build Arbor.HttpClient.slnx --no-restore --configuration Release | |
| unit-tests: | |
| name: Unit Tests | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # Infrastructure safety net; independent from the 10-second runtime budget enforced in the test step. | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| # dotnet-trx uses this to post test result comments on pull requests. | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Build unit test projects | |
| run: > | |
| dotnet build Arbor.HttpClient.slnx | |
| --no-restore | |
| --configuration Release | |
| - name: Install test reporting tools | |
| run: | | |
| dotnet tool install -g dotnet-trx --version 1.0.1 | |
| dotnet tool install -g trxlog2html --version 1.0.4 | |
| - name: Run unit tests | |
| run: | | |
| max_runtime_seconds=10 | |
| start_time=$(date +%s) | |
| # Hang guard for each suite; strict "under 10s total" requirement is enforced below. | |
| # Keep the shell guard slightly above the internal MTP timeout so MTP can emit diagnostics first. | |
| timeout 70 dotnet test src/Arbor.HttpClient.Core.Tests/Arbor.HttpClient.Core.Tests.csproj \ | |
| --no-restore \ | |
| --no-build \ | |
| --configuration Release \ | |
| -- \ | |
| --results-directory "$GITHUB_WORKSPACE/artifacts/test-results/unit/core" \ | |
| --report-xunit-trx \ | |
| --diagnostic \ | |
| --diagnostic-output-fileprefix core-tests \ | |
| --diagnostic-filelogger-synchronouswrite \ | |
| --timeout 60s \ | |
| --long-running 10 \ | |
| --report-xunit-trx-filename unit-tests.trx || { | |
| exit_code=$? | |
| if [ "$exit_code" -eq 124 ]; then | |
| echo "::error::Core unit tests timed out (hang detected)." | |
| ps -ef | grep '[d]otnet' || true | |
| fi | |
| exit "$exit_code" | |
| } | |
| # Keep the shell guard slightly above the internal MTP timeout so MTP can emit diagnostics first. | |
| timeout 70 dotnet test src/Arbor.HttpClient.Storage.Sqlite.Tests/Arbor.HttpClient.Storage.Sqlite.Tests.csproj \ | |
| --no-restore \ | |
| --no-build \ | |
| --configuration Release \ | |
| -- \ | |
| --results-directory "$GITHUB_WORKSPACE/artifacts/test-results/unit/sqlite" \ | |
| --report-xunit-trx \ | |
| --report-xunit-trx-filename sqlite-tests.trx \ | |
| --diagnostic \ | |
| --diagnostic-output-fileprefix sqlite-tests \ | |
| --diagnostic-filelogger-synchronouswrite \ | |
| --timeout 60s \ | |
| --long-running 10 || { | |
| exit_code=$? | |
| if [ "$exit_code" -eq 124 ]; then | |
| echo "::error::SQLite unit tests timed out (hang detected)." | |
| ps -ef | grep '[d]otnet' || true | |
| fi | |
| exit "$exit_code" | |
| } | |
| elapsed=$(( $(date +%s) - start_time )) | |
| echo "Unit test execution time: ${elapsed}s" | |
| if [ "$elapsed" -ge "$max_runtime_seconds" ]; then | |
| echo "::error::Unit tests exceeded ${max_runtime_seconds} seconds (${elapsed}s)." | |
| exit 1 | |
| fi | |
| - name: Report unit test results | |
| if: always() | |
| run: | | |
| test -f artifacts/test-results/unit/core/unit-tests.trx || { echo "::error::Missing core unit test TRX results."; exit 1; } | |
| test -f artifacts/test-results/unit/sqlite/sqlite-tests.trx || { echo "::error::Missing SQLite unit test TRX results."; exit 1; } | |
| trx --path artifacts/test-results/unit/core --verbosity normal | |
| trx --path artifacts/test-results/unit/sqlite --verbosity normal | |
| - name: Generate unit test HTML report | |
| if: always() | |
| run: | | |
| mkdir -p artifacts/test-results/unit/html | |
| trxlog2html -i artifacts/test-results/unit/core/unit-tests.trx -o artifacts/test-results/unit/html/core-tests.html | |
| trxlog2html -i artifacts/test-results/unit/sqlite/sqlite-tests.trx -o artifacts/test-results/unit/html/sqlite-tests.html | |
| - name: Upload unit test results | |
| if: always() | |
| id: upload-unit-trx | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: unit-test-results | |
| path: | | |
| artifacts/test-results/unit/core/* | |
| artifacts/test-results/unit/sqlite/* | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Upload unit test HTML report | |
| if: always() | |
| id: upload-unit-html | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: unit-test-results-html | |
| path: artifacts/test-results/unit/html/*.html | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Summarize unit test artifacts | |
| if: always() | |
| run: | | |
| { | |
| echo "## Unit Test Artifacts" | |
| echo "| Report | Download |" | |
| echo "| ------ | -------- |" | |
| echo "| TRX results | [${{ steps.upload-unit-trx.outputs.artifact-url }}](${{ steps.upload-unit-trx.outputs.artifact-url }}) |" | |
| echo "| HTML report | [${{ steps.upload-unit-html.outputs.artifact-url }}](${{ steps.upload-unit-html.outputs.artifact-url }}) |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| integration-tests: | |
| name: Integration Tests | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| # dotnet-trx uses this to post test result comments on pull requests. | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Install test reporting tools | |
| run: | | |
| dotnet tool install -g dotnet-trx --version 1.0.1 | |
| dotnet tool install -g trxlog2html --version 1.0.4 | |
| - name: Run integration tests | |
| run: > | |
| dotnet test src/Arbor.HttpClient.Core.Integration.Tests/Arbor.HttpClient.Core.Integration.Tests.csproj | |
| --no-restore | |
| --configuration Release | |
| -- | |
| --results-directory "${{ github.workspace }}/artifacts/test-results/integration" | |
| --report-xunit-trx | |
| --report-xunit-trx-filename integration-tests.trx | |
| --diagnostic | |
| --diagnostic-output-fileprefix integration-tests | |
| --diagnostic-filelogger-synchronouswrite | |
| --timeout 120s | |
| --long-running 15 | |
| - name: Report integration test results | |
| if: always() | |
| run: | | |
| test -f artifacts/test-results/integration/integration-tests.trx || { echo "::error::Missing integration test TRX results."; exit 1; } | |
| trx --path artifacts/test-results/integration --verbosity normal | |
| - name: Generate integration test HTML report | |
| if: always() | |
| run: | | |
| mkdir -p artifacts/test-results/integration/html | |
| trxlog2html -i artifacts/test-results/integration/integration-tests.trx -o artifacts/test-results/integration/html/index.html | |
| - name: Upload integration test diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: integration-test-results | |
| path: artifacts/test-results/integration/* | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Upload integration test HTML report | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: integration-test-results-html | |
| path: artifacts/test-results/integration/html/*.html | |
| retention-days: 14 | |
| if-no-files-found: error | |
| release-verification: | |
| name: Release Build Verification | |
| runs-on: windows-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Set version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $ver = "1.0.${{ github.run_number }}.0" | |
| $tag = "v1.0.${{ github.run_number }}" | |
| "VERSION=$ver" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| "TAG=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| Write-Host "Version: $ver Tag: $tag" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Install SBOM tool | |
| run: dotnet tool install --global microsoft.sbom.dotnettool --version 4.1.5 | |
| - name: Audit NuGet dependencies for vulnerabilities | |
| run: dotnet list Arbor.HttpClient.slnx package --vulnerable --include-transitive | |
| - name: Build | |
| run: dotnet build Arbor.HttpClient.slnx --no-restore --configuration Release | |
| - name: Run unit tests | |
| run: > | |
| dotnet test src/Arbor.HttpClient.Core.Tests/Arbor.HttpClient.Core.Tests.csproj | |
| --no-restore | |
| --configuration Release | |
| -- | |
| --results-directory "${{ github.workspace }}/artifacts/test-results/release-verification/core" | |
| --diagnostic | |
| --diagnostic-output-fileprefix release-verification-core-tests | |
| --diagnostic-filelogger-synchronouswrite | |
| --timeout 60s | |
| --long-running 10 | |
| - name: Run SQLite storage tests | |
| run: > | |
| dotnet test src/Arbor.HttpClient.Storage.Sqlite.Tests/Arbor.HttpClient.Storage.Sqlite.Tests.csproj | |
| --no-restore | |
| --configuration Release | |
| -- | |
| --results-directory "${{ github.workspace }}/artifacts/test-results/release-verification/sqlite" | |
| --diagnostic | |
| --diagnostic-output-fileprefix release-verification-sqlite-tests | |
| --diagnostic-filelogger-synchronouswrite | |
| --timeout 60s | |
| --long-running 10 | |
| - name: Publish win-x64 self-contained | |
| run: > | |
| dotnet publish src/Arbor.HttpClient.Desktop/Arbor.HttpClient.Desktop.csproj | |
| --configuration Release | |
| -r win-x64 | |
| --self-contained true | |
| -o publish/win-x64 | |
| - name: Build release artifacts | |
| shell: pwsh | |
| run: scripts/Build-Release.ps1 -Version "${{ steps.version.outputs.VERSION }}" | |
| # Attest build provenance is skipped here — it requires id-token: write which is | |
| # not granted to CI jobs. Attestation runs only in the release workflow on main. | |
| # GitHub Release creation is intentionally omitted — this job verifies the packaging | |
| # pipeline only; the actual release is created by the release workflow on main. | |
| ux-screenshots: | |
| name: UX Screenshot Verification | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Generate UX screenshots | |
| env: | |
| SCREENSHOT_OUTPUT_DIR: docs/screenshots | |
| run: > | |
| dotnet test src/Arbor.HttpClient.Desktop.E2E.Tests/Arbor.HttpClient.Desktop.E2E.Tests.csproj | |
| --no-restore | |
| --configuration Release | |
| -- | |
| --filter-trait "Category=Screenshots" | |
| --results-directory "${{ github.workspace }}/artifacts/test-results/ux-screenshots" | |
| --diagnostic | |
| --diagnostic-output-fileprefix ux-screenshots | |
| --diagnostic-filelogger-synchronouswrite | |
| --timeout 120s | |
| --long-running 30 | |
| - name: Upload UX screenshots | |
| if: always() | |
| id: upload-ux-screenshots | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: ux-screenshots | |
| path: docs/screenshots/*.png | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Summarize UX screenshots | |
| if: always() | |
| run: | | |
| { | |
| echo "## UX Screenshot Verification" | |
| echo "" | |
| echo "Screenshots generated by headless Avalonia rendering." | |
| echo "Download the artifact to inspect the current UI state." | |
| echo "" | |
| echo "| Artifact | Download |" | |
| echo "| -------- | -------- |" | |
| echo "| UX screenshots | [${{ steps.upload-ux-screenshots.outputs.artifact-url }}](${{ steps.upload-ux-screenshots.outputs.artifact-url }}) |" | |
| echo "" | |
| echo "To regenerate locally and commit to docs/screenshots/:" | |
| echo '```bash' | |
| echo './scripts/take-screenshots.sh' | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| e2e-tests: | |
| name: E2E Tests | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 6 | |
| permissions: | |
| contents: read | |
| # dotnet-trx uses this to post test result comments on pull requests. | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Install test reporting tools | |
| run: | | |
| dotnet tool install -g dotnet-trx --version 1.0.1 | |
| dotnet tool install -g trxlog2html --version 1.0.4 | |
| - name: Build E2E test project | |
| run: > | |
| dotnet build src/Arbor.HttpClient.Desktop.E2E.Tests/Arbor.HttpClient.Desktop.E2E.Tests.csproj | |
| --no-restore | |
| --configuration Release | |
| - name: Run E2E tests | |
| env: | |
| SCREENSHOT_OUTPUT_DIR: TestResults/screenshots | |
| run: | | |
| max_runtime_seconds=120 | |
| start_time=$(date +%s) | |
| # Timeout is a hang guard; runtime budget is enforced explicitly below. | |
| # This E2E job runs only integration-tagged tests; screenshot tests run in ux-screenshots. | |
| # Keep the shell guard at the stated 2-minute cap and leave a small gap for MTP diagnostics first. | |
| timeout 120 dotnet test src/Arbor.HttpClient.Desktop.E2E.Tests/Arbor.HttpClient.Desktop.E2E.Tests.csproj \ | |
| --no-restore \ | |
| --no-build \ | |
| --configuration Release \ | |
| -- \ | |
| --filter-trait Category=Integration \ | |
| --filter-not-trait Category=Screenshots \ | |
| --results-directory "$GITHUB_WORKSPACE/artifacts/test-results/e2e" \ | |
| --report-xunit-trx \ | |
| --report-xunit-trx-filename e2e-tests.trx \ | |
| --diagnostic \ | |
| --diagnostic-output-fileprefix e2e-tests \ | |
| --diagnostic-filelogger-synchronouswrite \ | |
| --timeout 115s \ | |
| --long-running 30 || { | |
| exit_code=$? | |
| if [ "$exit_code" -eq 124 ]; then | |
| echo "::error::E2E tests timed out at 2 minutes (hang detected)." | |
| ps -ef | grep '[d]otnet' || true | |
| fi | |
| exit "$exit_code" | |
| } | |
| elapsed=$(( $(date +%s) - start_time )) | |
| echo "E2E integration runtime: ${elapsed}s" | |
| if [ "$elapsed" -gt "$max_runtime_seconds" ]; then | |
| echo "::error::E2E integration tests exceeded ${max_runtime_seconds} seconds (${elapsed}s)." | |
| exit 1 | |
| fi | |
| - name: Report E2E test results | |
| if: always() | |
| run: | | |
| test -f artifacts/test-results/e2e/e2e-tests.trx || { echo "::error::Missing E2E test TRX results."; exit 1; } | |
| trx --path artifacts/test-results/e2e --verbosity normal | |
| - name: Generate E2E test HTML report | |
| if: always() | |
| run: | | |
| mkdir -p artifacts/test-results/e2e/html | |
| trxlog2html -i artifacts/test-results/e2e/e2e-tests.trx -o artifacts/test-results/e2e/html/index.html | |
| - name: Upload E2E test results | |
| if: always() | |
| id: upload-e2e-trx | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: e2e-test-results | |
| path: artifacts/test-results/e2e/* | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Upload E2E test HTML report | |
| if: always() | |
| id: upload-e2e-html | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: e2e-test-results-html | |
| path: artifacts/test-results/e2e/html/*.html | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Upload E2E screenshots | |
| if: always() | |
| id: upload-e2e-screenshots | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: e2e-screenshots | |
| path: TestResults/screenshots/*.png | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| - name: Summarize E2E test artifacts | |
| if: always() | |
| run: | | |
| { | |
| echo "## E2E Test Artifacts" | |
| echo "| Report | Download |" | |
| echo "| ------ | -------- |" | |
| echo "| TRX results | [${{ steps.upload-e2e-trx.outputs.artifact-url }}](${{ steps.upload-e2e-trx.outputs.artifact-url }}) |" | |
| echo "| HTML report | [${{ steps.upload-e2e-html.outputs.artifact-url }}](${{ steps.upload-e2e-html.outputs.artifact-url }}) |" | |
| if [ -n "${{ steps.upload-e2e-screenshots.outputs.artifact-url }}" ]; then | |
| echo "| Screenshots | [${{ steps.upload-e2e-screenshots.outputs.artifact-url }}](${{ steps.upload-e2e-screenshots.outputs.artifact-url }}) |" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Dedicated coverage job (see docs/coverage.md): kept separate from unit-tests | |
| # so coverage instrumentation overhead does not affect its strict runtime budget. | |
| code-coverage: | |
| name: Code Coverage | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Build | |
| run: dotnet build Arbor.HttpClient.slnx --no-restore --configuration Release | |
| - name: Run tests with coverage | |
| run: | | |
| run_with_coverage() { | |
| local project_name="$1" | |
| shift | |
| dotnet test "src/${project_name}/${project_name}.csproj" \ | |
| --no-restore \ | |
| --no-build \ | |
| --configuration Release \ | |
| -- \ | |
| --coverage \ | |
| --coverage-output-format cobertura \ | |
| --coverage-output "$GITHUB_WORKSPACE/artifacts/coverage/${project_name}.cobertura.xml" \ | |
| "$@" | |
| } | |
| run_with_coverage Arbor.HttpClient.Core.Tests | |
| run_with_coverage Arbor.HttpClient.Core.Integration.Tests | |
| run_with_coverage Arbor.HttpClient.Storage.Sqlite.Tests | |
| run_with_coverage Arbor.HttpClient.Desktop.E2E.Tests --filter-not-trait Category=Screenshots | |
| - name: Generate coverage report | |
| run: | | |
| dotnet tool install -g dotnet-reportgenerator-globaltool | |
| reportgenerator \ | |
| -reports:"artifacts/coverage/*.cobertura.xml" \ | |
| -targetdir:"artifacts/coverage/report" \ | |
| -reporttypes:"Html;MarkdownSummaryGithub" | |
| { | |
| echo "## Code Coverage" | |
| cat artifacts/coverage/report/SummaryGithub.md | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload coverage artifacts | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: code-coverage | |
| path: artifacts/coverage/* | |
| retention-days: 14 | |
| if-no-files-found: error | |
| system-tests: | |
| name: System Tests (Runner UI Automation) | |
| needs: build | |
| runs-on: windows-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| with: | |
| persist-credentials: false | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore | |
| run: dotnet restore Arbor.HttpClient.slnx --locked-mode | |
| - name: Audit NuGet dependencies for vulnerabilities | |
| run: dotnet list Arbor.HttpClient.slnx package --vulnerable --include-transitive | |
| - name: Publish win-x64 self-contained | |
| shell: pwsh | |
| run: | | |
| dotnet publish src/Arbor.HttpClient.Desktop/Arbor.HttpClient.Desktop.csproj ` | |
| --configuration Release ` | |
| -r win-x64 ` | |
| --self-contained true ` | |
| -o publish/win-x64 ` | |
| -v quiet | |
| # ----------------------------------------------------------------------- | |
| # Launch the real application on the runner desktop, take screenshots, | |
| # and record a short video. All steps use continue-on-error because GitHub | |
| # hosted runners run as a service (non-interactive session) — the probe | |
| # degrades gracefully when a display is unavailable. | |
| # ----------------------------------------------------------------------- | |
| - name: Run system test — light theme (launch app, record, screenshot) | |
| id: system-test-light | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path 'system-test-results/light' | Out-Null | |
| scripts/Invoke-UIAutomationProbe.ps1 ` | |
| -ExePath 'publish/win-x64/Arbor.HttpClient.Desktop.exe' ` | |
| -Theme Light | |
| foreach ($f in @('desktop-before.png', 'desktop-after.png', 'app-recording.mp4', 'validation-frame.png')) { | |
| if (Test-Path $f) { Move-Item $f (Join-Path 'system-test-results/light' $f) -Force } | |
| } | |
| - name: Run system test — dark theme (launch app, screenshot) | |
| id: system-test-dark | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path 'system-test-results/dark' | Out-Null | |
| scripts/Invoke-UIAutomationProbe.ps1 ` | |
| -ExePath 'publish/win-x64/Arbor.HttpClient.Desktop.exe' ` | |
| -Theme Dark | |
| foreach ($f in @('desktop-before.png', 'desktop-after.png')) { | |
| if (Test-Path $f) { Move-Item $f (Join-Path 'system-test-results/dark' $f) -Force } | |
| } | |
| - name: Upload system test artifacts | |
| if: always() | |
| id: upload-system-artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: system-test-results-${{ github.run_number }} | |
| path: system-test-results/ | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| - name: Write system test summary | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $lines = @( | |
| "## System Test Results (Runner UI Automation)" | |
| "" | |
| "| Step | Outcome |" | |
| "|---|---|" | |
| "| System test light theme | ${{ steps.system-test-light.outcome }} |" | |
| "| System test dark theme | ${{ steps.system-test-dark.outcome }} |" | |
| "" | |
| ) | |
| $artifactUrl = '${{ steps.upload-system-artifacts.outputs.artifact-url }}' | |
| if ($artifactUrl) { | |
| $lines += "Artifacts: [$artifactUrl]($artifactUrl)" | |
| } | |
| $lines | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append |