Uno winui-runtime-tests
Build, install, and run runtime tests against the WinUI (WinAppSDK) SamplesApp on Windows. Use when testing against native WinUI to validate parity with Uno.
git clone https://github.com/unoplatform/uno
T=$(mktemp -d) && git clone --depth=1 https://github.com/unoplatform/uno "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/winui-runtime-tests" ~/.claude/skills/unoplatform-uno-winui-runtime-tests && rm -rf "$T"
.claude/skills/winui-runtime-tests/SKILL.mdUser Input
$ARGUMENTS
You MUST consider the user input before proceeding (if not empty).
Overview
You are executing the WinUI Runtime Tests Skill. This skill builds the WinAppSDK SamplesApp as an MSIX package, installs it, and runs runtime tests via the app execution alias. This is used to validate behavior against native WinUI — the reference implementation that Uno Platform targets.
Requirements: Windows only. Requires MSBuild (Visual Studio) and PowerShell (
pwsh preferred, powershell.exe works too).
Helper scripts are in the same directory as this SKILL.md (
.claude/skills/winui-runtime-tests/):
— One-time certificate generation + trust (requires admin elevation once)setup-cert.ps1
— Remove old package + install built MSIXinstall-msix.ps1
— Launch app and wait for test resultsrun-tests.ps1
— Uninstall packagecleanup.ps1
Critical Pitfalls (Read First)
These are real issues encountered in practice — not theoretical:
-
MSBuild switch syntax in bash: Forward-slash switches (
,/r
) are interpreted as Unix paths by bash. Always use dash syntax:/p:
,-restore
,-t:Publish
.-p:Configuration=Release -
PowerShell from bash: Complex PowerShell with
,$()
,$_
gets mangled by bash escaping. Always write a.Property
file and run with.ps1
instead of inlinepwsh -NoProfile -File script.ps1
strings. The helper scripts in this skill directory handle this for you.-Command -
PowerShell drive may not work: On some environments theCert:
PSDrive and PKI module are unavailable (even in Windows PowerShell 5.1). Always useCert:\
command-line tool instead ofcertutil
,New-SelfSignedCertificate
,Import-PfxCertificate
, etc. TheExport-Certificate
tool works everywhere.certutil -
Signing certificate: CI uses a secret cert. Locally,
generates a unique self-signed cert per machine viasetup-cert.ps1
. The private key never leaves the local cert store and is not committed to source control. The thumbprint is saved tocertreq
(user home — shared across all worktrees).~/.uno-dev-cert-thumbprint -
Certificate trust for MSIX install: The self-signed cert must be in
(Trusted Root CAs) beforeLocalMachine\Root
will accept it. This requires admin elevation on first run only —Add-AppxPackage
handles this viasetup-cert.ps1
. On subsequent runs it's already trusted.Start-Process -Verb RunAs -
Use
for signing: Always build withPackageCertificateThumbprint
(NOT-p:PackageCertificateThumbprint=<thumbprint>
). The thumbprint approach is the most reliable across environments. ThePackageCertificateKeyFile
approach often fails withPackageCertificateKeyFile
.APPX0105: Cannot import the key file -
Existing package conflict: If a SamplesApp is already installed with the same version,
fails withAdd-AppxPackage
. Always remove existing packages first —0x80073CFB
handles this.install-msix.ps1 -
crosstargeting_override.props MUST be set: The SamplesApp.Windows project targets
=$(NetPreviousWinAppSDK)
. You MUST create/setnet9.0-windows10.0.19041.0
withsrc/crosstargeting_override.props
. If the file is missing or set to a different value (e.g.,<UnoTargetFrameworkOverride>net9.0-windows10.0.19041.0</UnoTargetFrameworkOverride>
), the build will pull in Skia/Wasm projects as transitive dependencies — those projects require source generators to have already run and will fail with hundreds ofnet10.0
errors. "File not found" is NOT acceptable — always create it.CS0535: does not implement interface member 'DependencyObject.XXX' -
MAX_PATH (260 chars): The PRI resource generator uses Win32 APIs with the 260-char path limit. If you see
/PRI175
errors, shorten the repo path or usePRI252
drive mapping.subst -
Results file is UTF-16 encoded XML: The NUnit XML results file is written in UTF-16 encoding. The
tool will often fail with token limits on this file, andRead
/head
will show garbled double-spaced output. Always use the python parsing snippet from Phase 6 instead of the Read tool.cat -
Graphics3DGL Windows TFM:
only builds Skia TFMs by default. SamplesApp.Windows references it but MSBuild picks the SkiaUno.WinUI.Graphics3DGL.csproj
build, causingnet9.0
errors. Fix: Before building SamplesApp.Windows, restore Graphics3DGL with the Windows TFM enabled:CS0012: The type 'Grid' is defined in an assembly that is not referenced"$MSBUILD" "src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj" \ -restore -v:m -p:BuildGraphics3DGLForWindows=true \ -p:Platform=x64 -p:Configuration=ReleaseAlso ensure
hasSamplesApp.Windows.csproj
on that ProjectReference.AdditionalProperties="BuildGraphics3DGLForWindows=true" -
ParseArgs base64 truncation:
usesApp.Tests.cs:ParseArgs
to parse CLI args, which breaks base64 filter values containingSplit('=')
padding. The filter is silently dropped and all tests run instead of filtered tests. If you see all tests running when a filter was provided, verify that=
usesParseArgs
to split only on the firstSplit('=', 2)
.=
Execution Workflow
Phase 0: Parse User Input
Determine what to run from the user's input:
- All tests: No filter needed
- Specific test class: e.g.,
→ resolve to fully qualified nameGiven_Button - Specific test method: e.g.,
→ resolve to fully qualified nameGiven_Button.When_ContentSet - Multiple tests: Pipe-separated list of fully qualified names
If the user provides partial names, search
src/Uno.UI.RuntimeTests/Tests/ to resolve fully qualified test names (namespace + class + method).
Phase 1: Prerequisites
Run all prerequisite checks/setup in sequence.
1a. Detect PowerShell
Determine which PowerShell to use for helper scripts:
if command -v pwsh &>/dev/null; then PS_CMD="pwsh" else PS_CMD="powershell.exe" fi
Use
$PS_CMD -NoProfile -ExecutionPolicy Bypass -File script.ps1 for all script invocations.
1b. Find MSBuild
IMPORTANT: Use
-prerelease -all flags — without them, vswhere skips preview/insiders installations and may return nothing:
MSBUILD=$(pwsh -NoProfile -Command "& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -all -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe'" 2>/dev/null)
Why PowerShell instead of bash: The
vswhere.exe path contains (x86) which bash interprets as a subshell. Using pwsh -Command with single-quoted paths avoids this. If you must use bash directly, escape or quote the path carefully.
If
vswhere returns nothing even with -prerelease -all, verify Visual Studio is installed and includes the MSBuild component.
1c. Set crosstargeting_override.props (MANDATORY)
The file
src/crosstargeting_override.props MUST exist and contain net9.0-windows10.0.19041.0. Without it, MSBuild resolves all target frameworks and pulls in Skia/Wasm projects that fail to build.
Check and fix:
# If file doesn't exist, create from sample if [ ! -f src/crosstargeting_override.props ]; then cp src/crosstargeting_override.props.sample src/crosstargeting_override.props fi
Then ensure it contains:
<UnoTargetFrameworkOverride>net9.0-windows10.0.19041.0</UnoTargetFrameworkOverride>
If it's set to anything else (e.g.,
net10.0 for Skia development), change it to net9.0-windows10.0.19041.0 before building. Remember to restore the previous value after WinUI testing is complete if the user was working with a different target.
Symptoms of a wrong/missing override:
— Uno.UI.Skia is being built as a transitive dependencyCS0535: does not implement interface member 'DependencyObject.XXX'
— Uno.UI.Tasks hasn't been built for the expected configurationMSB4062: ResourcesGenerationTask_v0 could not be loaded- Hundreds of errors from
— dead giveawayUno.UI.Skia.csproj::TargetFramework=net9.0
1d. Setup signing certificate (first time)
Run the setup script. It's idempotent — skips if already set up:
SKILL_DIR=".claude/skills/winui-runtime-tests" $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/setup-cert.ps1"
On first run on a new machine, this will:
- Generate a new self-signed cert with subject
viaCN=Uno Platformcertreq - Save the thumbprint to
(shared across worktrees)~/.uno-dev-cert-thumbprint - Prompt UAC elevation to trust the cert in
LocalMachine\Root
On subsequent runs (including from other worktrees), it detects the cert is already set up and exits immediately.
1e. Read the thumbprint
After
setup-cert.ps1 runs, read the thumbprint for the build step:
THUMBPRINT=$(cat ~/.uno-dev-cert-thumbprint)
1f. Restore Graphics3DGL with Windows TFM
The
Uno.WinUI.Graphics3DGL project only builds Skia targets by default. SamplesApp.Windows references it, so it must also have a Windows TFM available:
"$MSBUILD" "src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj" \ -restore -v:m -p:BuildGraphics3DGLForWindows=true \ -p:Platform=x64 -p:Configuration=Release
This is idempotent — safe to run every time. Skip only if you know Graphics3DGL was already restored with the Windows TFM.
Phase 2: Build the MSIX Package
CRITICAL: Set bash timeout to 600000 (10 minutes). NEVER cancel builds.
"$MSBUILD" "src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj" \ -restore -t:Publish -m -v:m \ -p:Configuration=Release \ -p:Platform=x64 \ -p:RuntimeIdentifier=win-x64 \ -p:GenerateAppxPackageOnBuild=true \ -p:PackageCertificateThumbprint=$THUMBPRINT
Key points:
- Use dash syntax (
, not-restore
) — forward slashes are eaten by bash/r - Use
— most reliable signing methodPackageCertificateThumbprint - The cert must be in the user's cert store already (Phase 1d handles this)
Build failure diagnostics
| Error | Cause | Fix |
|---|---|---|
from | missing or set to wrong TFM. MSBuild resolves all TFMs and pulls in Skia which needs source generators. | Set override to (Phase 1c). This is the #1 most common build failure. |
| Uno.UI.Tasks.v0.dll not built; cascading from wrong TFM pulling in unexpected dependencies | Set override to (Phase 1c) |
| TFM mismatch | Set to |
/ | MAX_PATH >= 260 chars | Shorten repo path or drive |
| No cert in store | Run (Phase 1d) |
| Used instead of thumbprint | Switch to |
| Bash mangled as path | Use dash syntax: |
| Graphics3DGL built for Skia only | Run Phase 1f (restore Graphics3DGL with Windows TFM) |
| Graphics3DGL not restored with Windows TFM | Run Phase 1f before building SamplesApp |
Phase 3: Install the MSIX Package
Run the install helper script:
$PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/install-msix.ps1" \ -RepoRoot "."
This handles removing existing packages and finding/installing the bundle.
Install failure diagnostics
| Error | Cause | Fix |
|---|---|---|
| Cert not in LocalMachine\Root | Run |
| Old package present | Script handles this automatically |
| MSIX was built without signing | Rebuild with thumbprint (Phase 2) |
| MSIX built with signing disabled has structural issues | Rebuild with signing enabled |
Phase 4: Construct the Filter
If running specific tests (not all tests):
-
Format the filter string: Fully qualified test names, pipe-separated
- Single:
Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Given_Control.When_Scenario - Multiple:
Test1|Test2|Test3
- Single:
-
Base64 encode in bash:
FILTER=$(echo -n "fully.qualified.TestName" | base64 -w 0)Or for PowerShell:
$filter = [Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes("fully.qualified.TestName"))
Phase 5: Run Tests
Run the test helper script:
RESULTS_FILE="$(pwd)/winui-test-results.xml" # Without filter (all tests): $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/run-tests.ps1" \ -ResultsFile "$RESULTS_FILE" # With filter: $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/run-tests.ps1" \ -ResultsFile "$RESULTS_FILE" -Filter "$FILTER"
Set bash timeout to 600000 (10 minutes).
Results are output in NUnit XML format.
Phase 6: Parse Results and Cleanup
IMPORTANT: The results file is UTF-16 encoded XML. Do NOT use the
Read tool (token limits) or head/cat (garbled output). Use this python snippet:
python3 -c " import re with open('RESULTS_FILE_PATH', 'r', encoding='utf-16') as f: content = f.read() m = re.search(r'<test-run[^>]+total=\"(\d+)\"[^>]+passed=\"(\d+)\"[^>]+failed=\"(\d+)\"[^>]+skipped=\"(\d+)\"', content) if m: total, passed, failed, skipped = m.groups() print(f'TOTAL: {total} PASSED: {passed} FAILED: {failed} SKIPPED: {skipped}') print() for m in re.finditer(r'<test-case\s+name=\"([^\"]+)\"[^>]*result=\"(\w+)\"', content): name, result = m.groups() status = ' PASS' if result == 'Passed' else '**FAIL' if result == 'Failed' else ' SKIP' print(f'{status} {name}') "
Replace
RESULTS_FILE_PATH with the actual path.
To extract failure messages for failed tests:
python3 -c " import re with open('RESULTS_FILE_PATH', 'r', encoding='utf-16') as f: content = f.read() for m in re.finditer(r'<test-case\s+name=\"([^\"]+)\"[^>]*result=\"Failed\".*?<message>(.*?)</message>', content, re.DOTALL): name, msg = m.groups() print(f'FAILED: {name}') print(f' {msg.strip()[:500]}') print() "
Cleanup steps:
- Restore
: If you changed it in Phase 1c (e.g., fromcrosstargeting_override.props
tonet10.0
), restore it to the user's previous value so their Skia/Wasm development workflow isn't broken.net9.0-windows10.0.19041.0
Interpreting WinUI failures: Tests that fail on WinUI represent the native WinUI behavior. If a test passes on Uno but fails on WinUI (or vice versa), this reveals a parity gap. Use the
[PlatformCondition] attribute to exclude tests from WinUI:
[TestMethod] [PlatformCondition(ConditionMode.Exclude, RuntimeTestPlatforms.NativeWinUI)] public void When_Test_That_Diverges_On_WinUI() { ... }
Quick Reference: Complete Bash Flow
This is the exact sequence to execute. Copy-paste each step:
# --- Config --- SKILL_DIR=".claude/skills/winui-runtime-tests" PS_CMD="pwsh" # or "powershell.exe" if pwsh unavailable MSBUILD=$($PS_CMD -NoProfile -Command "& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -prerelease -all -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe'" 2>/dev/null) # --- Phase 1a: Save and set crosstargeting override (MANDATORY) --- # Save current value if it exists so we can restore later OVERRIDE_FILE="src/crosstargeting_override.props" OVERRIDE_BACKUP="" if [ -f "$OVERRIDE_FILE" ]; then OVERRIDE_BACKUP=$(cat "$OVERRIDE_FILE") fi # Create from sample if missing, then set to Windows TFM if [ ! -f "$OVERRIDE_FILE" ]; then cp src/crosstargeting_override.props.sample "$OVERRIDE_FILE" fi # Ensure it contains net9.0-windows10.0.19041.0 # (use Edit tool to set UnoTargetFrameworkOverride) # --- Phase 1b: Setup cert (idempotent, first time prompts UAC) --- $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/setup-cert.ps1" THUMBPRINT=$(cat ~/.uno-dev-cert-thumbprint) # --- Phase 1f: Restore Graphics3DGL with Windows TFM (timeout: 600000ms) --- "$MSBUILD" "src/AddIns/Uno.WinUI.Graphics3DGL/Uno.WinUI.Graphics3DGL.csproj" \ -restore -v:m -p:BuildGraphics3DGLForWindows=true \ -p:Platform=x64 -p:Configuration=Release # --- Phase 2: Build MSIX (timeout: 600000ms) --- "$MSBUILD" "src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj" \ -restore -t:Publish -m -v:m \ -p:Configuration=Release -p:Platform=x64 -p:RuntimeIdentifier=win-x64 \ -p:GenerateAppxPackageOnBuild=true \ -p:PackageCertificateThumbprint=$THUMBPRINT # --- Phase 3: Install MSIX --- $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/install-msix.ps1" -RepoRoot "." # --- Phase 4+5: Run tests (timeout: 600000ms) --- # Without filter: $PS_CMD -NoProfile -ExecutionPolicy Bypass -File "$SKILL_DIR/run-tests.ps1" \ -ResultsFile "$(pwd)/winui-test-results.xml" # With filter: # FILTER=$(echo -n "Fully.Qualified.TestName" | base64 -w 0) # $PS_CMD ... -Filter "$FILTER" # --- Phase 6: Parse results (UTF-16 XML — use python, NOT Read tool) --- python3 -c " import re with open('winui-test-results.xml', 'r', encoding='utf-16') as f: content = f.read() m = re.search(r'<test-run[^>]+total=\"(\d+)\"[^>]+passed=\"(\d+)\"[^>]+failed=\"(\d+)\"[^>]+skipped=\"(\d+)\"', content) if m: total, passed, failed, skipped = m.groups() print(f'TOTAL: {total} PASSED: {passed} FAILED: {failed} SKIPPED: {skipped}') print() for m in re.finditer(r'<test-case\s+name=\"([^\"]+)\"[^>]*result=\"(\w+)\"', content): name, result = m.groups() status = ' PASS' if result == 'Passed' else '**FAIL' if result == 'Failed' else ' SKIP' print(f'{status} {name}') " # --- Cleanup --- # IMPORTANT: Restore crosstargeting_override.props to its previous value
Technical Reference
App Execution Alias
- Alias:
unosamplesapp.exe - Registered by: MSIX package via
(Package.appxmanifest
)uap5:AppExecutionAlias - Requires: MSIX package installed via
Add-AppxPackage
Command-Line Arguments
| Argument | Description |
|---|---|
| Absolute path for NUnit XML results output |
| Base64-encoded, pipe-separated test filter |
| CI shard index (not typically used locally) |
| CI total shards (not typically used locally) |
Filter Encoding
- Format: Base64-encoded UTF-8 string
- Separator:
(pipe) between multiple fully qualified test names| - Delivery: Via
CLI argument OR--runtime-test-filter
env varUITEST_RUNTIME_TESTS_FILTER
Key Files
- Project:
src/SamplesApp/SamplesApp.Windows/SamplesApp.Windows.csproj - App manifest:
src/SamplesApp/SamplesApp.Windows/Package.appxmanifest - CI YAML:
build/ci/tests/.azure-devops-tests-winappsdk.yml - CI test script:
build/test-scripts/run-winui-runtime-tests.ps1 - Entry point:
src/SamplesApp/SamplesApp.Shared/App.Tests.cs - Test location:
src/Uno.UI.RuntimeTests/Tests/ - Local thumbprint:
(user home, shared across worktrees)~/.uno-dev-cert-thumbprint
Build Details
| Property | Value |
|---|---|
| Build tool | MSBuild via dash syntax (not , not switches) |
| Target framework | () |
| Platform | x64 |
| Output | MSIX bundle in |
| WinAppSDK version | 1.8 (check csproj for exact version) |
| Manifest publisher | (cert subject must match) |
| Cert generation | (per-machine, private key stays local) |
| Runtime installer | |
| MAX_PATH budget | Keep full repo path under ~200 chars |
Signing: What Works vs What Doesn't
| Approach | Reliability | Notes |
|---|---|---|
| Best | Cert must be in user store. Use this. |
+ | Fragile | errors in many environments |
Build unsigned + post-sign | Works | More steps, but viable fallback |
| Broken | Produces unsigned MSIX that can't install (publisher namespace mismatch) |
Certificate Management: What Works vs What Doesn't
| Approach | Reliability | Notes |
|---|---|---|
(generate) | Best | Works everywhere, unique key per machine |
| Best | For importing existing PFX |
(elevated) | Best | For trusting; needs admin once |
PSDrive + PKI module | Broken | drive missing in some environments |
| Broken | Depends on drive |
| Broken | Depends on PKI module |
| Committing PFX to repo | Security risk | Private key exposed to all repo users |