Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
microsoft avatar

Winui Ui Testing

  • 118 installs
  • 373 repo stars
  • Updated August 3, 2026
  • microsoft/win-dev-skills

winui-ui-testing is an agent skill for automated WinUI 3 UI testing via winapp ui batch scripts, assertions, and accessibility audits.

About

The winui-ui-testing skill automates WinUI 3 app validation through scripted batch testing instead of slow interactive exploration. It prefers generating a ui-tests.ps1 script that exercises AutomationId-based elements, navigation, value checks on TextBox, ComboBox, and ToggleSwitch controls, flyouts, dialogs, file pickers, persistence, and accessibility coverage in one pass. The winapp ui CLI provides status, inspect, wait-for, invoke, click, set-value, screenshot, and scroll verbs with JSON output for parsing. Tests use a Test-UI helper that records pass and fail results, captures screenshots per meaningful state, audits missing AutomationId on app controls while excluding OS chrome, and writes test-results.json before exiting non-zero on failures. Agents reuse a running app PID from the build step rather than relaunching, derive HWND from list-windows excluding PopupHost, and use throw inside scriptblocks instead of exit to avoid terminating the suite. Use for WinUI UI regression, automated assertions, and accessibility audits before release.

  • Generates ui-tests.ps1 batch scripts for repeatable WinUI validation.
  • Uses winapp ui wait-for, invoke, set-value, and screenshot verbs.
  • Captures pass/fail results and test-results.json with non-zero exit on failure.
  • Audits AutomationId coverage while excluding OS picker and window chrome.
  • Reuses running app PID and avoids relaunching during test iterations.

Winui Ui Testing by the numbers

  • 118 all-time installs (skills.sh)
  • +14 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #953 of 2,153 Testing & QA skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

winui-ui-testing capabilities & compatibility

Capabilities
batch ui tests.ps1 generation and execution · automationid based wait, invoke, and value asser · screenshot capture for visual state review · accessibility automationid coverage audit · structured pass/fail json results output
Use cases
testing · frontend
Platforms
Windows
npx skills add https://github.com/microsoft/win-dev-skills --skill winui-ui-testing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs118
repo stars373
Last updatedAugust 3, 2026
Repositorymicrosoft/win-dev-skills

How do I automatically test WinUI 3 UI elements and accessibility without manual clicking each iteration?

Generate and run WinUI 3 batch ui-tests.ps1 scripts with winapp ui assertions, interactions, screenshots, and accessibility audits.

Who is it for?

WinUI 3 developers who need repeatable UI regression and AutomationId-based test scripts.

Skip if: Skip for initial UI discovery-only sessions, non-WinUI platforms, or build and compile error diagnosis.

When should I use this skill?

User asks to test WinUI UI, generate ui-tests.ps1, or audit AutomationId coverage on a running app.

What you get

A ui-tests.ps1 run with pass/fail results, screenshots, accessibility audit, and JSON output for review.

Files

SKILL.mdMarkdownGitHub ↗

Approach

The goal of this skill is to validate UI and app functionality automatically, without manual interaction, by exercising the app's UI elements, verifying their state, and asserting that the app behaves as expected under test conditions.

There are two main approaches: 1. Interactive exploration — manually run the app, use winapp ui <command> to explore the UI tree, find AutomationIds, verify element properties, and test functionality interactively. This is useful for discovery, but slow and expensive if repeated for every test iteration. 2. Scripted batch testing — generate a ui-tests.ps1 script that exercises all UI elements and asserts expected behavior in one pass. This allows you to run the tests automatically, capture results, and iterate quickly without manually interacting with the app each time.

Unless the user asked for interactive exploration, or you are unfamiliar with the code/app or need to explore the UI tree to discover AutomationIds for hidden or dynamically generated elements (flyouts, dialogs, lazy-loaded content), prefer scripted batch testing — it is faster, repeatable, and produces a record of pass/fail results that can be reviewed and acted on.

winapp ui Verbs

status, inspect, search, get-property, get-value, screenshot, invoke, click, set-value, focus, scroll, scroll-into-view, wait-for, list-windows, get-focused. Run winapp ui --cli-schema for the complete command structure as JSON, or winapp ui <verb> --help for any single verb.

Step 1: Use the Running App

If the app is already running, use its PID. Do NOT relaunch — use the PID already captured from the build step. If the app is not running, build and launch it using the guidance in the winui-dev-workflow skill.

Step 2: Write the Test Script

If you wrote the code: Skip inspect — you already know all the AutomationIds and control structure from the XAML and code-behind. Write tests directly from that knowledge. Inspect misses popups, flyouts, dialogs, and lazy-loaded content anyway.

If you're verifying code you didn't write: Run inspect first to discover the UI:

winapp ui inspect -a <PID> --interactive

Then read the XAML files to find AutomationIds that aren't currently visible (flyout items, dialog buttons, secondary pages).

Create a ui-tests.ps1 file that tests all the app's requirements in one pass:

# ui-tests.ps1
param([Parameter(Mandatory)][int]$AppPid)
# NOTE: Do NOT name the parameter $Pid — it's read-only in PowerShell

$ErrorActionPreference = 'Continue'
$pass = 0; $fail = 0; $results = @()

# Get main window HWND (avoids PopupHost interference with JSON parsing)
$windows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json
$hwnd = ($windows | Where-Object { $_.title -ne "PopupHost" } | Select-Object -First 1).hwnd

function Test-UI {
    param([string]$Name, [scriptblock]$Script)
    # IMPORTANT: Inside $Script, use 'throw' to signal failure — NOT 'exit 1'
    # (exit terminates the entire script, not just the test)
    try {
        $output = & $Script 2>&1
        if ($LASTEXITCODE -eq 0) {
            $script:pass++; $script:results += @{ name = $Name; status = "PASS" }
        } else {
            $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$output" }
        }
    } catch {
        $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$_" }
    }
}

# ─── Element Existence ───
Test-UI "NavHome exists" { winapp ui wait-for "NavHome" -a $AppPid -t 3000 }
Test-UI "NavSettings exists" { winapp ui wait-for "NavSettings" -a $AppPid -t 3000 }

# ─── Navigation ───
Test-UI "Navigate to Settings" { winapp ui invoke "NavSettings" -a $AppPid }
Test-UI "Settings page loaded" { winapp ui wait-for "TxtUserName" -a $AppPid -t 3000 }

# ─── Interactions ───
Test-UI "Set username" { winapp ui set-value "TxtUserName" "TestUser" -a $AppPid }
Test-UI "Click Save" { winapp ui invoke "BtnSave" -a $AppPid }  # commits the TextBox binding
Test-UI "Username value set" {
    winapp ui wait-for "TxtUserName" -a $AppPid --value "TestUser" -t 2000
}

# ─── Value assertions for different control types ───
Test-UI "Theme is System default" {
    winapp ui wait-for "CmbTheme" -a $AppPid --value "System default" -t 2000
}
Test-UI "Logging is off" {
    winapp ui wait-for "TglLogging" -a $AppPid --value "Off" -t 2000
}

# ─── Accessibility Audit ───
# Only audit controls in the app's main window (exclude OS picker/popup controls)
$allElements = (winapp ui inspect -a $AppPid --interactive --json 2>$null | ConvertFrom-Json).elements
$appElements = @($allElements | Where-Object {
    $_.type -match 'Button|TextBox|ComboBox|CheckBox|ToggleSwitch|TabItem|Edit' -and
    $_.name -notmatch 'Minimize|Maximize|Close|System' -and          # window chrome
    $_.className -notmatch 'PickerHost|#32770|CabinetWClass'         # OS dialogs
})
$missingId = @($appElements | Where-Object { -not $_.automationId })
if ($missingId.Count -eq 0) {
    $pass++; $results += @{ name = "All app controls have AutomationId"; status = "PASS" }
} else {
    $fail++
    $names = ($missingId | ForEach-Object { "$($_.type) '$($_.name)'" }) -join ", "
    $results += @{ name = "AutomationId coverage"; status = "FAIL"; detail = "Missing: $names" }
}

# ─── State Screenshots (capture each meaningful state for visual review) ───
New-Item -ItemType Directory -Force -Path "screenshots" | Out-Null
winapp ui screenshot -a $AppPid -o "screenshots/01-initial.png" 2>$null
# ...take more screenshots after key interactions above (mode switches, dialogs opened, etc.)

# ─── Final Screenshot ───
winapp ui screenshot -a $AppPid -o "test-screenshot.png" 2>$null

# ─── Results ───
Write-Host "`nPassed: $pass | Failed: $fail"
$results | Where-Object { $_.status -eq "FAIL" } | ForEach-Object {
    Write-Host "  FAIL: $($_.name) — $($_.detail)" -ForegroundColor Red
}
$results | ConvertTo-Json | Out-File "test-results.json"
if ($fail -gt 0) { exit 1 } else { exit 0 }

What to Test

Write tests for every requirement from the user's prompt:

Requirement typeTest approach
"Has a button that does X"search to verify exists, invoke to click, wait-for --value to check result
"Text field shows value"wait-for "TxtName" --value "expected" — works for TextBox, TextBlock, labels
"Status bar contains text"wait-for "StatusBar" --value "words" --contains — substring match for dynamic content
"Dropdown is set to X"wait-for "CmbTheme" --value "Dark" — reads the selected item automatically
"Toggle is on/off"wait-for "TglFeature" --value "On" — reads the toggle state
"Navigation between pages"invoke nav item, wait-for a page-specific element to appear
"Open file dialog"invoke trigger, list-windows to find picker HWND, interact with -w
"Save file dialog"Same as open — find picker with list-windows, set-value filename, invoke Save
"Right-click context menu"click --right on element, invoke the flyout MenuItem
"Confirmation dialog"invoke trigger, search for dialog buttons, invoke Primary/Secondary/Close
"Data persists"Set values, invoke a button (to commit bindings), verify data file on disk (Get-Content + ConvertFrom-Json)
"All controls accessible"inspect --interactive --json + check all have AutomationId

Step 3: Run and Read Results

.\ui-tests.ps1 -AppPid <PID>

Read test-results.json for structured pass/fail. Only fix code if tests fail.

Step 3.5: Look at the Screenshots

UIA assertions don't see clipping, overlap, wrong theming, or controls bleeding past their container — UIA returns PASS while the app is visually broken. Capture screenshots with `winapp ui screenshot` and view each PNG.

Capture the initial state and any state after a major interaction (the State Screenshots block in the script template above handles this).

Visual checklist — fail the run if any item is `no`:

  • [ ] No unintended scrollbars
  • [ ] No text ending in that shouldn't be
  • [ ] Hero elements fully visible (not sliced)
  • [ ] Right-edge controls fully visible
  • [ ] No overlapping rows
  • [ ] Content uses the available width — no asymmetric dead zones (e.g. content pinned to one edge leaving empty space on the other)
  • [ ] Spacing intentional — not cramped, not unintentionally vast
  • [ ] Theming matches the user's ask (Light/Dark/HighContrast if relevant)
  • [ ] Focus/hover/error states render if tested

If the checklist fails, it's a bug — fix before declaring done. Window too small → grow per winui-design Step 4.

Step 4: Fix and Rerun (if the user asked for it)

If tests fail: 1. Read the failure details from test-results.json 2. Batch-fix all issues in one pass 3. Rebuild with .\BuildAndRun.ps1 (blocking mode — shows crash info if the fix broke something) 4. Rerun .\ui-tests.ps1 -AppPid <PID> (parse PID from the launched (PID: XXXXX) output)

Maximum 2 fix-and-rerun cycles. If the same tests keep failing after 2 cycles, report them as known issues and move on — do not keep iterating.

Assertion Reference

Use wait-for --value as the primary assertion — it uses a smart fallback chain that reads the right value for any control type:

Control type--value reads fromExample
TextBlock / LabelName propertywait-for "LblTitle" --value "Home"
TextBox / NumberBoxValuePatternwait-for "TxtName" --value "John"
RichEditBoxTextPatternwait-for "Editor" --value "Hello"
ComboBoxSelected item (SelectionPattern)wait-for "CmbTheme" --value "Dark"
ToggleSwitchToggle state (On/Off)wait-for "TglDark" --value "On"
CheckBoxToggle state (On/Off)wait-for "ChkAgree" --value "On"

Full assertion commands:

AssertionCommand
Element existswinapp ui wait-for "Id" -a PID -t 3000
Element has exact valuewinapp ui wait-for "Id" -a PID --value "expected" -t 3000
Value contains textwinapp ui wait-for "Id" -a PID --value "words" --contains -t 3000
Element gonewinapp ui wait-for "Id" -a PID --gone -t 3000
Specific propertywinapp ui wait-for "Id" -a PID -p IsEnabled --value "True" -t 3000
Button clickablewinapp ui invoke "Id" -a PID (exit code 0)
Set then verifywinapp ui set-value "Id" "text" -a PID then wait-for --value
Screenshotwinapp ui screenshot -a PID -o path.png
Dialog appearedwinapp ui list-windows -a PID --json (check window count)
Right-click menuwinapp ui click "Id" -a PID --right then wait-for menu item
Read raw propertywinapp ui get-property "Id" -a PID -p IsEnabled --json
Read current value (no wait)`(winapp ui get-value "Id" -a PID --json \
Scroll item into viewwinapp ui scroll-into-view "Id" -a PID — call before wait-for on virtualized ListView/repeater items below the fold
Set keyboard focuswinapp ui focus "Id" -a PID — cleaner than clicking another control to trigger a TextBox LostFocus commit

Testing File Pickers

File/folder pickers (FileOpenPicker, FileSavePicker, FolderPicker) run in a separate PickerHost process but are fully interactable. The picker appears as an owned dialog window.

# 1. Trigger the picker
winapp ui invoke "BtnOpenFile" -a $AppPid

# 2. Find the picker window (it's a dialog owned by the app window)
Start-Sleep 1
$allWindows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json
$picker = $allWindows | Where-Object { $_.title -match "Open|Save" }
$pickerHwnd = $picker.hwnd

# 3. Interact with the picker using -w <HWND>
#    Type a filename:
winapp ui set-value "FileNameControlHost" "test.txt" -w $pickerHwnd
#    Click Open/Save:
winapp ui invoke "Open" -w $pickerHwnd     # or "Save", "Cancel"
#    Or cancel:
winapp ui invoke "Cancel" -w $pickerHwnd

# 4. Verify the app processed the file
winapp ui wait-for "StatusBar" -a $AppPid -p Name --value "opened" -t 3000

Tip: Use winapp ui inspect -w <pickerHwnd> --interactive to discover the picker's controls — they include the folder tree, file list, filename textbox, and Open/Cancel buttons.

Testing Context Menus and Flyouts

MenuFlyouts and ContextFlyouts are fully testable. They appear in the UI automation tree when open.

# 1. Right-click to open a ContextFlyout
winapp ui click "LstItems" -a $AppPid --right
Start-Sleep 0.5

# 2. The flyout MenuItems appear in the tree immediately
#    Find them with inspect or search:
winapp ui inspect -a $AppPid --interactive   # shows MnuCopy, MnuDelete, etc.

# 3. Click a flyout item
winapp ui invoke "MnuCopy" -a $AppPid

# 4. Verify the action
winapp ui wait-for "StatusText" -a $AppPid -p Name --value "Copied" -t 2000

For MenuBar flyouts (File, Edit, View menus):

# Click the menu header to open
winapp ui invoke "FileMenu" -a $AppPid
Start-Sleep 0.5
# Click the sub-item
winapp ui invoke "MenuSaveAs" -a $AppPid

Testing ContentDialogs

ContentDialogs are in-app controls (same window) — they appear directly in the UI tree when shown.

# 1. Trigger the dialog
winapp ui invoke "BtnDelete" -a $AppPid
Start-Sleep 0.5

# 2. The dialog buttons appear in the tree
#    For a standard confirmation dialog:
winapp ui search "Primary" -a $AppPid --json   # finds the primary button
winapp ui invoke "Primary" -a $AppPid           # click "Yes"/"Delete"/"Save"
#    Or:
winapp ui invoke "Secondary" -a $AppPid         # click "No"/"Don't Save"
winapp ui invoke "Close" -a $AppPid             # click "Cancel"

# 3. Wait for dialog to dismiss
winapp ui wait-for "Primary" -a $AppPid --gone -t 3000

Tip: ContentDialog buttons often don't have custom AutomationIds — use inspect to find the actual selector (slug or text match).

Key Gotchas

  • `set-value` does NOT commit default TextBox bindings — WinUI 3 x:Bind TwoWay on TextBox.Text updates the ViewModel on LostFocus by default. UIA set-value changes the text but doesn't trigger focus events. Fix: apps should use UpdateSourceTrigger=PropertyChanged on TextBox bindings (see design skill). If the app doesn't, invoke a button or click another element after set-value to trigger LostFocus.
  • Verify persistence via the data file, not UI relaunch — killing and relaunching a packaged app from a test script is fragile (MSIX registration timing, PID issues). Instead, check the data file on disk: Get-Content $dataFile | ConvertFrom-Json and verify expected values.
  • Use `$AppPid` not `$Pid`$Pid is a read-only automatic variable in PowerShell
  • Use `--value` without `-p` — it auto-detects the right UIA pattern (TextPattern → ValuePattern → TogglePattern → SelectionPattern → Name). Only use -p PropertyName --value when you need a specific property like IsEnabled
  • File pickers need `-w <HWND>` — they run in a separate PickerHost process, so -a PID won't find them. Use list-windows to discover the picker HWND first
  • Flyouts need a short `Start-Sleep` after triggering — the menu items appear in the tree asynchronously

Related skills

FAQ

Should I relaunch the app for each WinUI test run?

No. Reuse the running app PID from the build step instead of relaunching between test iterations.

When should I use winui-ui-testing?

When you need scripted batch UI validation with assertions, screenshots, and accessibility coverage.

Is winui-ui-testing safe to install?

Review the Security Audits panel on this page before installing in production.

Testing & QAtestingfrontend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.