Skip to content

Commit 4e293b8

Browse files
committed
Add build CI and tagged package releases
Build every toolkit on Windows, run filtered tests with hang and session timeouts, and pack the 17 library and .NET tool packages. Allow the window tests to run on an explicitly enabled interactive desktop runner. Publish GitHub Releases from version tags or manual runs after the build passes, with a dry-run option. Include repository metadata in packages and document release and NuGet.org publishing setup.
1 parent 4ce7abe commit 4e293b8

7 files changed

Lines changed: 275 additions & 0 deletions

File tree

‎.github/actionlint.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
self-hosted-runner:
2+
labels:
3+
- desktop

‎.github/scripts/Test.ps1‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[CmdletBinding()]
2+
param(
3+
[ValidateSet('Headless', 'Desktop')]
4+
[string] $Suite = 'Headless',
5+
[string] $Configuration = 'Release',
6+
[string] $ResultsDirectory = 'artifacts/test-results'
7+
)
8+
9+
$ErrorActionPreference = 'Stop'
10+
$ResultsDirectory = [System.IO.Path]::GetFullPath($ResultsDirectory)
11+
12+
if ($Suite -eq 'Headless') {
13+
$projects = @(
14+
'DesktopDriver.Contracts.Tests',
15+
'DesktopDriver.Runtime.Tests',
16+
'DesktopDriver.Protocol.Tests',
17+
'DesktopDriver.Cli.Tests',
18+
'DesktopDriver.Avalonia.Tests',
19+
'DesktopDriver.Maui.Tests'
20+
)
21+
$groups = @($projects | ForEach-Object {
22+
@{ Project = $_; Filter = 'FullyQualifiedName~StockSharp.DesktopDriver.Tests' }
23+
})
24+
$groups += @{ Project = 'DesktopDriver.Mcp.Tests'; Filter = 'FullyQualifiedName~StockSharp.DesktopDriver.Tests.Mcp.ServerSurfaceTests' }
25+
} else {
26+
$groups = @(
27+
@{ Project = 'DesktopDriver.Wpf.Tests'; Filter = 'FullyQualifiedName~StockSharp.DesktopDriver.Tests.Wpf' },
28+
@{ Project = 'DesktopDriver.Mcp.Tests'; Filter = 'FullyQualifiedName~StockSharp.DesktopDriver.Tests.Mcp' }
29+
)
30+
}
31+
32+
# Test assemblies run in sequence, so process startup does not compete with another suite.
33+
foreach ($group in $groups) {
34+
$report = 'trx;LogFileName=' + $group.Project + '.trx'
35+
dotnet test $group.Project --configuration $Configuration --no-build --no-restore `
36+
--filter $group.Filter --blame-hang-timeout 120s `
37+
--logger $report --results-directory $ResultsDirectory `
38+
-- RunConfiguration.TestSessionTimeout=300000
39+
40+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
41+
}

‎.github/workflows/build.yml‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
workflow_dispatch:
8+
workflow_call:
9+
inputs:
10+
version:
11+
description: Package and assembly version for a release build.
12+
required: false
13+
type: string
14+
default: ''
15+
16+
permissions:
17+
contents: read
18+
19+
env:
20+
DOTNET_CLI_TELEMETRY_OPTOUT: '1'
21+
DOTNET_NOLOGO: '1'
22+
23+
jobs:
24+
build-and-test:
25+
runs-on: windows-latest
26+
timeout-minutes: 40
27+
defaults:
28+
run:
29+
shell: pwsh
30+
env:
31+
PACKAGE_VERSION: ${{ inputs.version }}
32+
steps:
33+
- uses: actions/checkout@v7
34+
35+
- uses: actions/setup-dotnet@v6
36+
with:
37+
dotnet-version: 10.0.400
38+
39+
- name: Install the MAUI Windows workload
40+
run: dotnet workload install maui-windows --skip-manifest-update
41+
42+
- run: dotnet restore DesktopDriver.slnx
43+
44+
- name: Build every toolkit and tool
45+
run: |
46+
$versionArgument = @()
47+
if ($env:PACKAGE_VERSION) { $versionArgument = @('-p:Version=' + $env:PACKAGE_VERSION) }
48+
dotnet build DesktopDriver.slnx --configuration Release --no-restore @versionArgument
49+
50+
- name: Run tests that need no desktop session
51+
run: ./.github/scripts/Test.ps1 -Suite Headless
52+
53+
- name: Pack libraries and command-line tools
54+
run: |
55+
$versionArgument = @()
56+
if ($env:PACKAGE_VERSION) { $versionArgument = @('-p:Version=' + $env:PACKAGE_VERSION) }
57+
dotnet pack DesktopDriver.slnx --configuration Release --no-build --no-restore --output artifacts/packages @versionArgument
58+
59+
- uses: actions/upload-artifact@v7
60+
if: always()
61+
with:
62+
name: test-results
63+
path: artifacts/test-results/
64+
retention-days: 7
65+
66+
- uses: actions/upload-artifact@v7
67+
with:
68+
name: packages
69+
path: artifacts/packages/*.nupkg
70+
if-no-files-found: error
71+
retention-days: 7
72+
73+
# Enable this only for a runner started in an interactive Windows desktop session.
74+
desktop:
75+
if: vars.DESKTOP_TESTS_ENABLED == 'true' && github.event_name != 'pull_request'
76+
needs: build-and-test
77+
runs-on: [self-hosted, Windows, X64, desktop]
78+
timeout-minutes: 30
79+
defaults:
80+
run:
81+
shell: pwsh
82+
steps:
83+
- uses: actions/checkout@v7
84+
85+
- uses: actions/setup-dotnet@v6
86+
with:
87+
dotnet-version: 10.0.400
88+
89+
- run: dotnet build DesktopDriver.slnx --configuration Release
90+
91+
- name: Run tests that open real windows
92+
run: ./.github/scripts/Test.ps1 -Suite Desktop
93+
94+
- uses: actions/upload-artifact@v7
95+
if: always()
96+
with:
97+
name: desktop-test-results
98+
path: artifacts/test-results/
99+
retention-days: 7

‎.github/workflows/release.yml‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: release
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: Version without the leading v, for example 1.0.0 or 1.0.0-beta.1.
10+
required: true
11+
type: string
12+
dry_run:
13+
description: Build, test and pack without creating a tag or release.
14+
required: true
15+
type: boolean
16+
default: true
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
prepare:
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 5
25+
outputs:
26+
version: ${{ steps.version.outputs.version }}
27+
steps:
28+
- id: version
29+
env:
30+
TYPED: ${{ inputs.version }}
31+
shell: bash
32+
run: |
33+
version="${TYPED:-${GITHUB_REF_NAME#v}}"
34+
if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
35+
echo "::error::Use a version such as 1.0.0 or 1.0.0-beta.1."
36+
exit 1
37+
fi
38+
echo "version=$version" >> "$GITHUB_OUTPUT"
39+
40+
package:
41+
needs: prepare
42+
uses: ./.github/workflows/build.yml
43+
with:
44+
version: ${{ needs.prepare.outputs.version }}
45+
46+
publish:
47+
if: github.event_name == 'push' || !inputs.dry_run
48+
needs: [prepare, package]
49+
runs-on: ubuntu-latest
50+
timeout-minutes: 10
51+
permissions:
52+
contents: write
53+
env:
54+
VERSION: ${{ needs.prepare.outputs.version }}
55+
GH_TOKEN: ${{ github.token }}
56+
steps:
57+
- uses: actions/download-artifact@v8
58+
with:
59+
name: packages
60+
path: packages
61+
62+
- name: Publish the GitHub release
63+
shell: bash
64+
run: |
65+
options=()
66+
if [[ "$VERSION" == *-* ]]; then options+=(--prerelease); fi
67+
notes="Contains the StockSharp.DesktopDriver NuGet libraries and the desktop-driver and desktop-driver-mcp .NET tool packages. Requires .NET 10. WPF and the MAUI platform backend target Windows."
68+
gh release create "v$VERSION" packages/*.nupkg \
69+
--repo "$GITHUB_REPOSITORY" \
70+
--target "$GITHUB_SHA" \
71+
--title "DesktopDriver $VERSION" \
72+
--notes "$notes" \
73+
--generate-notes "${options[@]}"

‎Directory.Build.props‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<PackageLicenseFile>LICENSE</PackageLicenseFile>
2121
<PackageReadmeFile>README.md</PackageReadmeFile>
2222
<PackageProjectUrl>https://stocksharp.com</PackageProjectUrl>
23+
<RepositoryUrl>https://github.com/StockSharp/DesktopDriver.Mcp</RepositoryUrl>
24+
<RepositoryType>git</RepositoryType>
2325
<PackageTags>ui-automation;testing;mcp;avalonia;wpf;maui</PackageTags>
2426
</PropertyGroup>
2527

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ From an agent: register the MCP server, see [DesktopDriver.Mcp](DesktopDriver.Mc
109109
- [What answers for which control](docs/adapters.md), and how to add an adapter
110110
- [The command line](docs/cli.md)
111111
- [The MCP server](DesktopDriver.Mcp/README.md)
112+
- [Builds, test suites and releases](docs/releasing.md)
112113

113114
## Build and test
114115

‎docs/releasing.md‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Building and releasing packages
2+
3+
The [build workflow](../.github/workflows/build.yml) runs on pushes to `master` and pull requests. It
4+
builds the whole solution on Windows with .NET 10.0.400 and the MAUI Windows workload, runs the tests
5+
that need no desktop session, and uploads the `.nupkg` files and test reports as Actions artifacts.
6+
Tests always have a filter, a two-minute hang timeout and a five-minute session timeout. Assemblies
7+
run in sequence.
8+
9+
`Contracts`, `Runtime` and `Cli` contain unit tests. `Protocol` exercises a real host and client with
10+
Avalonia Headless. The Avalonia suite uses headless controls, and the MAUI suite exercises controls
11+
without their native platform handlers. The MCP surface tests start the server without opening an
12+
application window.
13+
14+
The WPF screenshot tests and MCP application tests open real windows. To include these in CI, register
15+
a Windows x64 self-hosted runner with the `desktop` label, start it in an interactive desktop session,
16+
and set the repository variable `DESKTOP_TESTS_ENABLED` to `true`. The runner needs .NET 10.0.400 and
17+
the MAUI Windows workload. This job runs on trusted branch pushes and release runs; pull requests
18+
use the hosted runner. Without that configuration, desktop tests can be run locally after a Release
19+
build:
20+
21+
```powershell
22+
./.github/scripts/Test.ps1 -Suite Desktop
23+
```
24+
25+
## GitHub releases
26+
27+
The [release workflow](../.github/workflows/release.yml) follows the tag or manual release model used
28+
by [StockSharp/Odysseus](https://github.com/StockSharp/Odysseus/blob/main/.github/workflows/release.yml).
29+
For DesktopDriver, the assets are NuGet library and .NET tool packages. A release rebuilds, tests and
30+
packs the tagged source before publishing. All packages use the same version, including dependencies
31+
between DesktopDriver packages; tests and the sample application are not packed.
32+
33+
To publish version `1.0.0`, push the tag on the commit to release:
34+
35+
```sh
36+
git tag v1.0.0
37+
git push origin v1.0.0
38+
```
39+
40+
Alternatively, open **Actions → release → Run workflow**, enter a version without `v`, and turn off
41+
`dry_run` to publish. The manual run creates the tag on the selected commit after the build passes.
42+
Keep `dry_run` enabled to check a release build without creating a tag or release. A version with a
43+
prerelease suffix, such as `1.0.0-beta.1`, creates a GitHub prerelease.
44+
45+
The workflow uses GitHub's automatic `GITHUB_TOKEN` with write access to repository contents only
46+
for the publish job. No separate secret is needed for a GitHub release. It publishes `.nupkg` assets
47+
to GitHub Releases; it does not upload them to NuGet.org. A release version that already exists is
48+
refused rather than replaced.
49+
50+
## NuGet.org
51+
52+
Publishing to NuGet.org is a separate configuration. It can use
53+
[NuGet trusted publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing):
54+
register this repository and the publishing workflow with the NuGet package owner, then exchange a
55+
GitHub Actions identity for a short-lived publishing key with `NuGet/login`. A GitHub push credential
56+
or `GITHUB_TOKEN` does not grant publishing access to NuGet.org.

0 commit comments

Comments
 (0)