Skip to content

Extract draft persistence/restore loop into DraftWorkflow #1100

Extract draft persistence/restore loop into DraftWorkflow

Extract draft persistence/restore loop into DraftWorkflow #1100

Workflow file for this run

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