명령줄에서 실행 중인 Windows 애플리케이션을 검사하고 상호 작용합니다. AI 에이전트 및 개발자가 UI 테스트, 디버깅 및 자동화에 사용합니다.
개요
winapp ui Windows 앱 UI를 검사하고 상호 작용하기 위한 명령을 제공합니다.
UIA(Windows UI 자동화)를 사용합니다. WPF, WinForms, Win32, Electron 및 WinUI 3 등 모든 Windows 앱에서 작동합니다.
대부분의 명령은 UIA 패턴을 통해 앱을 구동합니다(입력 삽입 없음). ui click 는 예외이며 지원하지 InvokePattern않는 컨트롤에 실제 마우스 시뮬레이션을 사용합니다.
빠른 시작
# Connect to any app and see its UI tree
winapp ui inspect -a notepad
# Find specific elements
winapp ui search Button -a notepad
# Activate an element
winapp ui invoke Close -a notepad
# Take a screenshot
winapp ui screenshot -a notepad
앱 대상 지정
프로세스 이름별
winapp ui inspect -a notepad
winapp ui inspect -a slack # auto-picks visible window for multi-process apps
winapp ui inspect -a imageresizer # partial match: finds PowerToys.ImageResizer
창 제목별
winapp ui inspect -a "LICENSE - Notepad"
winapp ui inspect -a "Fix WinApp" # partial title match
PID별
winapp ui inspect -a 12345
HWND(안정적 - 탭/타이틀 변경 내용 유지)
# Discover HWNDs
winapp ui list-windows -a Terminal
→ HWND 985238: "🤖 Testing" (WindowsTerminal, PID 21228)
→ HWND 131906: "Fix WinApp" (WindowsTerminal, PID 21228)
# Target specific window
winapp ui inspect -w 131906
winapp ui screenshot -w 131906
안정적인 대상 지정을 위해 검색 -w 에 사용합니다-a. 여러 창과 일치하는 경우 -a 명령은 사용자가 선택할 HWND를 사용하여 나열합니다.
선택기
검사/검색 출력에 [brackets] 표시된 선택기를 사용하는 대상 요소입니다.
세 가지 유형의 선택기가 있습니다.
| 선택기 | 의미 | Example |
|---|---|---|
MinimizeButton |
AutomationId(고유할 때 표시됨 - 안정적이고 기본 설정) | winapp ui invoke MinimizeButton -a myapp |
btn-close-d1a0 |
의미 체계 슬러그(고유 AutomationId가 없는 경우 표시됨) | winapp ui invoke btn-close-d1a0 -a myapp |
Submit |
Name/AutomationId에 대한 일반 텍스트 검색(대/소문자를 구분하지 않는 부분 문자열) | winapp ui invoke Submit -a myapp |
AutomationId 선택기는 XAML에서 개발자 집합 식별자AutomationProperties.AutomationId 입니다.
AutomationId가 전체 UI 트리 inspect 에서 고유하고 search 선택기로 직접 표시되면 유지되는 레이아웃 변경, 지역화 및 트리 재구성이 유지됩니다.
슬러그 선택기 (예: btn-close-d1a0)는 고유한 AutomationId가 없을 때 생성됩니다.
형식: prefix-name-hash. 해시는 요소 ID의 유효성을 검사하지만 UI가 변경된 후 부실할 수 있습니다.
출력 형식 검사
이 inspect 명령은 색이 지정된 출력을 사용하여 요소 트리를 표시합니다(선택기(청록색 선택기, 녹색 이름, 회색 메타데이터).
TabView Tab (0,-1 1200x48)
TabListView List (4,-1 1100x48)
tab-newtab-5f5b TabItem "New Tab" (14,-1 200x48)
NewTabButton SplitButton "New Tab" [collapsed] (1104,5 96x36)
Found 10 elements (--depth 3). Use the first token as selector, e.g.: winapp ui invoke TabView -a terminal
각 줄의 첫 번째 단어 는 선택기입니다. 다른 ui 명령과 함께 사용합니다.
요소에 고유한 AutomationId가 있는 경우 직접 사용됩니다(예: TabViewNewTabButton).
고유한 AutomationId가 없으면 생성된 슬러그(예: tab-newtab-5f5b)가 사용됩니다.
의미 체계 슬러그
슬러그는 다음과 같은 형식 prefix-normalizedname-hash 을 사용합니다.
- 접두사 — 3자 형식 약어(btn, txt, chk, cmb, itm, tab, img, lbl, pn, win, grp, lnk, mnu 등)
- normalizedname - AutomationId(기본 설정) 또는 Name의 소문자 영숫자, 최대 15자
- 해시 - 요소 RuntimeId의 4자 16진수 해시(요소 ID 유효성 검사)
슬러그는 셸로부터 안전하며(특수 문자 없음), 고유하며 인수로 직접 사용할 수 있습니다. 해시는 부실 검색을 제공합니다. 요소가 교체된 경우 다음이 표시됩니다. "요소가 변경되었을 수 있습니다. 검사를 다시 실행합니다."
이름이나 AutomationId가 없는 요소는 접두사 + 해시(예: pn-c8a3)만 표시합니다.
여러 일치 항목 구분
출력의 inspect/search 슬러그는 고유하지만 레이아웃 변경에서 변경할 수 있습니다. 여러 항목이 일치할 때 일반 형식 이름 또는 텍스트에 사용합니다. 선택기가 모호한 경우 CLI는 모든 일치 항목을 해당 슬러그와 인쇄하므로 올바른 항목을 선택하고 해당 슬러그로 다시 실행할 수 있습니다.
winapp ui search Button -a myapp # shows: btn-ok-a1b2 "OK", btn-cancel-c3d4 "Cancel"
winapp ui invoke btn-ok-a1b2 -a myapp # invoke using slug (preferred)
winapp ui invoke btn-cancel-c3d4 -a myapp # invoke the other Button by its slug
일반 텍스트 검색
일반 텍스트를 사용하여 요소를 검색합니다. 특별한 구문은 필요하지 않습니다.
winapp ui search Minimize -a notepad # finds elements with "Minimize" in Name or AutomationId
winapp ui search Close -a notepad # case-insensitive substring match
winapp ui invoke Minimize -a notepad # search + invoke in one step (disambiguates if needed)
winapp ui search "Save" -a notepad # find elements containing "Save"
winapp ui search "error" -a myapp # case-insensitive match
텍스트 검색이 여러 요소(예: 그룹, 단추 및 텍스트가 모두 같은 이름을 공유하는 SettingsExpander)와 일치하면 CLI는 호출 가능한 유일한 요소를 자동으로 선택합니다. 여러 항목을 호출할 수 있는 경우 슬러그가 있는 모든 일치 항목을 나열합니다.
호출할 수 없는 검색 결과(예: 단추 안의 TextBlock)의 경우 검색은 호출 가능한 가장 가까운 상위 항목(사용할 수 있는 부모 요소)을 invoke자동으로 표시합니다.
이는 모든 검색 선택기에서 작동합니다.
lbl-savechanges-a1b2 "Save changes" (120,40 80x20)
^ invoke via: btn-save-c3d4 "Save"
표면화된 선택기를 직접 사용할 수 있습니다.
winapp ui invoke btn-save-c3d4 -a myapp # invoke the parent Button
명령어
상태
앱에 연결하고 연결 정보를 표시합니다.
winapp ui status -a notepad
winapp ui status -a notepad --json
검사
UI 요소 트리를 봅니다. 출력은 계층 구조에 대해 2개의 공간 들여쓰기가 있는 의미 체계 슬러그를 보여 줍니다.
winapp ui inspect -a notepad # full window tree, depth 3
winapp ui inspect -a notepad --depth 5 # deeper tree
winapp ui inspect txt-searchbox-e5f6 -a notepad # subtree rooted at element
winapp ui inspect --ancestors btn-close-d1a2 -a notepad # walk up from element to root
winapp ui inspect -a myapp --interactive # invokable elements only, auto-depth 8
winapp ui inspect -a myapp --hide-disabled # hide disabled elements
winapp ui inspect -a myapp --hide-offscreen # hide offscreen elements
예제 출력(기본값):
win-aidevgalleryp-f1a3 "AI Dev Gallery Preview" (94,206 1280x1023)
pn-c8a3 (102,207 1264x1014)
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
itm-samples-3f2c "Samples" (102,330 72x62)
출력 예제(--interactive - 호출 가능한 요소만, 플랫 목록):
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
btn-close-d1a2 "Close" (1318,206 48x48)
itm-home-7b3e "Home" (102,268 72x62)
itm-samples-3f2c "Samples" (102,330 72x62)
itm-models-9a4f "Models" (102,392 72x62)
요소는 다음과 같은 상태 표식을 표시할 수 있습니다.
-
[on]/[off]/[indeterminate]— 토글/확인란 상태 -
[collapsed]/[expanded]— 트리, 콤보 상자, 메뉴 항목에 대한 확장/축소 상태 -
[scroll:v]/[scroll:h]/[scroll:vh]- 스크롤 가능한 컨테이너(세로, 가로 또는 둘 다) -
[offscreen]— 요소가 화면에 표시되지 않습니다. -
[disabled]— 요소를 사용할 수 없습니다. -
value="..."— 편집 가능한 요소의 현재 텍스트 콘텐츠(Name과 다른 경우)
search
선택기와 일치하는 요소를 찾습니다. 출력은 의미 체계 슬러그를 보여줍니다.
winapp ui search Button -a notepad # all buttons
winapp ui search Close -a notepad # finds elements with "Close" in name
winapp ui search SearchBox -a notepad # finds elements with "SearchBox" in name or AutomationId
winapp ui search Button --max 10 -a notepad # limit results
예시 출력:
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
btn-close-d1a2 "Close" (1318,206 48x48)
출력에 표시된 슬러그(예: btn-minimize-d1a0)는 다른 명령과 함께 직접 사용할 수 있습니다.
winapp ui invoke btn-minimize-d1a0 -a notepad
get-property
요소에서 속성 값을 읽습니다. 패턴별 상태(ToggleState, Value, IsSelected 등)를 포함합니다.
winapp ui get-property btn-submit-7a90 -a myapp # all properties
winapp ui get-property chk-checkbox-b2c3 -p ToggleState -a myapp # checkbox state
winapp ui get-property txt-textbox-a4b1 -p Value -a myapp # current text value
winapp ui get-property cmb-combobox-d5e6 -p ExpandCollapseState -a myapp # expanded or collapsed
screenshot
창 또는 요소를 PNG로 캡처합니다. 여러 창이 있는 경우(예: 앱 + 열린 대화 상자) 각 창이 연결된 단일 PNG로 합성됩니다.
winapp ui screenshot -a notepad # saves screenshot.png in cwd
winapp ui screenshot -a notepad --output my.png # custom filename
winapp ui screenshot -a notepad --json # returns file path as JSON
winapp ui screenshot -w 131906 # target specific HWND (+ its dialogs)
winapp ui screenshot txt-searchbox-e5f6 -a myapp # crop to element bounds
winapp ui screenshot -a myapp --capture-screen # capture from screen (includes popups/overlays)
대화 상자 또는 팝업이 열려 있으면 모든 창이 하나의 PNG로 합성되므로 단일 이미지에서 전체 UI 상태를 볼 수 있습니다.
팝업 메뉴, 드롭다운, 플라이아웃 또는 도구 설명 오버레이를 캡처해야 하는 경우에 사용합니다 --capture-screen . 모드에서 --capture-screen (그리고 빈 프레임이 감지된 후 다시 시도할 때) 대상 창이 먼저 포그라운드로 이동됩니다. 일반 창 캡처는 창을 이동하지 않습니다.
호출
프로그래밍 방식으로 요소를 활성화합니다(클릭 단추, 토글 확인란, 콤보 확장 상자).
winapp ui invoke btn-submit-7a90 -a myapp # by slug from inspect
winapp ui invoke btn-submit-a1b2 -a myapp # by slug from inspect/search
winapp ui invoke cmb-sizecombobox-b4c5 -a myapp # expand combo box
순서대로 패턴을 시도합니다. InvokePattern → TogglePattern → SelectionItemPattern → ExpandCollapsePattern.
click
마우스 시뮬레이션을 사용하여 화면 좌표에서 요소를 클릭합니다. 이를 지원하지 InvokePattern 않는 컨트롤(예: 열 머리글, 목록 항목)에 사용합니다.
winapp ui click btn-column1-a3f2 -a myapp # single click by slug
winapp ui click "Column1" -a myapp # single click by text search
winapp ui click btn-column1-a3f2 -a myapp --double # double-click
winapp ui click btn-column1-a3f2 -a myapp --right # right-click
set-value
편집 가능한 요소(TextBox/ComboBox의 텍스트, 슬라이더의 경우 숫자)에 값을 설정합니다.
winapp ui set-value txt-textbox-a4b1 "Hello world" -a notepad
winapp ui set-value sld-volume-b2c3 75 -a myapp
get-value
요소에서 현재 값을 읽습니다. 스마트 대체 체인을 사용합니다. TextPattern(RichEditBox, Document) → ValuePattern(TextBox, Slider) → SelectionPattern(ComboBox, RadioButton, TabView) → 이름(레이블).
winapp ui get-value doc-texteditor-53ad -a notepad # read full document text
winapp ui get-value SearchBox -a myapp # read TextBox content
winapp ui get-value CmbTheme -a myapp # read ComboBox selected item
winapp ui get-value sld-volume-b2c3 -a myapp # read Slider value
winapp ui get-value lbl-title-a1b2 -a myapp --json # JSON: { "elementId": "...", "text": "..." }
포커스
키보드 포커스를 요소로 이동합니다.
winapp ui focus txt-textbox-a4b1 -a notepad
보기로 스크롤
요소를 표시되는 영역으로 스크롤합니다.
winapp ui scroll-into-view itm-targetitem-c3d4 -a myapp
wait-for
요소가 나타나거나 사라지거나 값이 대상에 도달할 때까지 기다립니다.
winapp ui wait-for Button -a myapp --timeout 5000 # wait for any button
winapp ui wait-for btn-submit-7a90 -a myapp --timeout 5000 # wait for specific element
winapp ui wait-for CounterDisplay -a myapp --value "5" --timeout 5000 # wait for element value (smart fallback)
winapp ui wait-for lbl-status -a myapp --property Name --value "Done" --timeout 5000 # wait for specific property
winapp ui wait-for btn-submit-a1b2 --gone -a myapp --timeout 2000 # wait for element to disappear
winapp ui wait-for lbl-status -a myapp --value "Done" --contains # substring match instead of exact equality
스크롤
컨테이너 요소를 스크롤합니다. (세로 search scroll ) 또는 [scroll:h] (가로) 표식을 찾아 [scroll:v] 스크롤 가능한 컨테이너를 찾습니다.
# Find which elements are scrollable and in which direction
winapp ui search scroll -a myapp
# pn-scrollview-bfef Pane "scrollView" [scroll:v] (main content, vertical)
# pn-scrollviewer-bfb1 Pane "scrollViewer" [scroll:h] (horizontal list)
# Scroll the main content down
winapp ui scroll pn-scrollview-bfef --direction down -a myapp
# Jump to top/bottom
winapp ui scroll pn-scrollview-bfef --to bottom -a myapp
# If you target an element that's not scrollable, scroll walks up to find the nearest scrollable parent
winapp ui scroll itm-someitem-a1b2 --direction down -a myapp
집중하기
현재 키보드 포커스가 있는 요소를 표시합니다.
winapp ui get-focused -a myapp
list-windows
팝업 및 대화 상자를 포함하여 앱에 표시되는 모든 창을 나열합니다.
winapp ui list-windows -a imageresizer
winapp ui list-windows -a Terminal
winapp ui list-windows # all windows (no filter)
프레임워크 지원
| 프레임워크 | 검사 | search | 호출 | set-value | screenshot |
|---|---|---|---|---|---|
| WPF | ✅ 전체 트리 | ✅ 모든 속성 | ✅ 모든 패턴 | ✅ | ✅ |
| 윈폼 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Win32 | ✅ | ✅ | ✅ | ✅ | ✅ |
| WinUI 3 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Electron | ⚠️ 크롬 트리 | ⚠️ 제한됨 | ⚠️ 다양 | ⚠️ 다양 | ✅ |
| Flutter | ⚠️ 기본 | ⚠️ 기본 | ❌ 최소 | ❌ | ✅ |
Troubleshooting
| 오류 | 원인 | 솔루션 |
|---|---|---|
| "실행 중인 앱을 찾을 수 없음" | 앱이 실행되지 않거나 이름이 일치하지 않습니다. | 프로세스 이름 확인 또는 PID 사용 |
| "여러 창 일치" | 모호한 -a 값 |
나열된 옵션에서 사용 -w <HWND> |
| "여러 창이 있습니다." | 프로세스에 여러 창이 있습니다. | 특정 대상 지정에 사용 -w <HWND> |
| "선택기가 N 요소와 일치" | 모호한 레거시 선택기 | 출력에서 inspect 슬러그를 사용하거나 레거시 선택기에 추가 [0][1] |
| "요소가 변경되었을 수 있습니다." | 슬러그 해시가 현재 요소와 일치하지 않음 | 다시 실행 inspect 하거나 search 신선한 슬러그를 얻을 수 |
| "호출 패턴을 지원하지 않습니다." | 요소를 호출할 수 없습니다. | 요소를 사용하여 inspect 호출 가능한 자식 찾기 |
| "UIA 창을 찾을 수 없습니다." | UIA에서 프로세스를 볼 수 없습니다. |
list-windows HWND를 찾은 다음,-w |
| "창 크기가 0입니다." | 창 최소화 | 앱이 자동으로 복원됩니다. |
| 스크린샷에 없는 팝업/드롭다운 | PrintWindow는 오버레이를 캡처하지 않습니다. | 플래그 사용 --capture-screen |
일반적인 패턴
탐색 및 확인
winapp ui invoke btn-settings-a1b2 -a myapp # click a button
winapp ui wait-for pn-settingspage-c3d4 -a myapp # wait for page to load
winapp ui screenshot -a myapp --output settings.png # verify visually
텍스트 찾기 및 부모 호출
# Search shows invokable ancestor; invoke auto-walks to it
winapp ui invoke 'Save changes' -a myapp
# Or search first to see what matches, then invoke
winapp ui search "Save changes" -a myapp; winapp ui invoke btn-save-c3d4 -a myapp
중복 요소 구분
winapp ui search '#Image' -a myapp; winapp ui invoke itm-image-a2b3 -a myapp
팝업 오버레이가 있는 스크린샷
winapp ui set-value txt-searchbox-e5f6 "query" -a myapp; winapp ui screenshot -a myapp --capture-screen
탐색, 대기 및 확인(단일 체인)
winapp ui invoke btn-settings-a1b2 -a myapp; winapp ui wait-for pn-settingspage-c3d4 -a myapp --timeout 3000; winapp ui screenshot -a myapp -o settings.png
검색, 클릭 및 확인
winapp ui inspect -a myapp --interactive; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp
파일 대화 상자 상호 작용
파일 열기/저장 대화 상자는 UIA가 지원하는 표준 Windows 대화입니다.
# Trigger the dialog, find it, type the path, confirm
winapp ui invoke btn-openfilebtn-a2b3 -a myapp
winapp ui list-windows -a myapp # find dialog HWND
winapp ui set-value txt-1148-c4d5 "C:\path\to\file.png" -w <dialog-hwnd>
winapp ui invoke btn-open-e6f7 -w <dialog-hwnd>
특정 대화 상자의 실제 슬러그를 검색하는 데 사용합니다 inspect -w <dialog-hwnd> --interactive .
체인에 대한 이유 ; (아님 &&)
네이티브 CLI가 stderr에 쓰거나 ANSI 이스케이프 시퀀스를 사용할 때 PowerShell의 && 연산자가 중지할 수 있습니다. 대신 각 ; 명령을 무조건 실행하고 이 교착 상태를 방지합니다. 에이전트 워크플로에 더 적합합니다. 일반적으로 호출에 0이 아닌 종료가 있더라도 스크린샷을 실행하려고 합니다.
CI 테스트 패턴
스모크 테스트 및 UI 유효성 검사에 CI 파이프라인(GitHub Actions, Azure DevOps)에서 winapp ui 명령을 사용합니다.
wait-for
--value 을 사용하여 --property 어설션으로 작동합니다. 시간 제한 시 종료 코드 1을 반환하여 CI 단계가 자동으로 실패합니다.
GitHub Actions 시작 및 테스트
steps:
- name: Build
run: dotnet build MyApp.csproj -c Debug -p:Platform=x64
- name: Launch and test
run: |
$result = winapp run .\bin\x64\Debug\net8.0-windows10.0.26100.0\win-x64 --detach --json | ConvertFrom-Json
$appPid = $result.ProcessId
# Wait for window to initialize
winapp ui wait-for "Main Window" -a $appPid --timeout 30000
# Run tests — each wait-for exits non-zero on failure
winapp ui invoke "Login" -a $appPid
winapp ui wait-for "Dashboard" -a $appPid --timeout 10000
winapp ui screenshot -a $appPid -o dashboard.png
다음을 사용하여 요소 상태를 어설션합니다. wait-for
wait-for --value 요소의 값이 예상 문자열과 일치할 때까지 폴링합니다.(TextPattern → ValuePattern → SelectionPattern → Name)과 동일한 스마트 대체 get-value 를 사용합니다. 일치 시 종료 코드 0을 반환하고, 시간 제한 시 코드 1을 종료하여 CI 친화적인 어설션으로 만듭니다. 대신 특정 UIA 속성을 확인하는 데 사용합니다 --property .
# Assert: button click updated the counter (smart value fallback — works for TextBlock, TextBox, etc.)
winapp ui invoke "Counter Button" -a $pid
winapp ui wait-for "Counter Display" -a $pid --value "Count: 1" -t 5000
# Assert: text input was accepted
winapp ui set-value "Search Box" "hello world" -a $pid
winapp ui wait-for "Search Box" -a $pid --value "hello world" -t 3000
# Assert: checkbox was toggled (use --property for specific UIA properties)
winapp ui invoke "Dark Mode" -a $pid
winapp ui wait-for "Dark Mode" -a $pid --property ToggleState --value "On" -t 3000
# Assert: navigation happened (new page appeared)
winapp ui invoke "Settings" -a $pid
winapp ui wait-for "Settings Page" -a $pid -t 10000
# Assert: dialog was dismissed (element disappeared)
winapp ui invoke "Close" -a $pid
winapp ui wait-for "Dialog Title" -a $pid --gone -t 5000
JSON 출력을 사용하여 어설션
더 복잡한 어설션을 위해 PowerShell 또는 jq와 함께 사용합니다 --json .
모드에서
--json종료 코드 계약searchwait-for: 일치하는 요소가 없거나 대기 시간 초과(searchwait-for)가 없는 경우 명령은 완전히 구문 분석 가능한 결과 봉투를 stdout({ "matchCount": 0, ... }또는{ "found": false, "timedOut": true, ... })에 쓰고 종료 코드 1을 반환합니다. Stderr는 모드에서--json비어 있습니다(로거 출력은 표시되지 않습니다). 봉투 필드의 분기 또는 인체공학적 요소에$LASTEXITCODE따라 분기합니다.
# Assert: search found exactly one match
$result = winapp ui search "Submit" -a $pid --json | ConvertFrom-Json
if ($result.matchCount -ne 1) { throw "Expected 1 Submit button, found $($result.matchCount)" }
# Assert: element has expected properties
# inspect --json returns { windows: [{ hwnd, title, elements: [...] }] };
# each window's elements[] is the nested tree (children rendered via .children).
$tree = winapp ui inspect "Counter Display" -a $pid --json | ConvertFrom-Json
$counter = $tree.windows[0].elements[0]
if ($counter.name -ne "Count: 3") { throw "Counter value wrong: $($counter.name)" }
전체 스모크 테스트 예제
# Launch
$app = winapp run .\build-output --detach --json | ConvertFrom-Json
# Verify app loaded
winapp ui wait-for "Main Page" -a $app.ProcessId -t 30000
# Interact and assert
winapp ui invoke "Add Item" -a $app.ProcessId
winapp ui set-value "Item Name" "Test Item" -a $app.ProcessId
winapp ui invoke "Save" -a $app.ProcessId
winapp ui wait-for "Test Item" -a $app.ProcessId -t 5000 # assert item appeared in list
winapp ui wait-for "Save" -a $app.ProcessId --gone -t 3000 # assert save dialog closed
# Visual verification
winapp ui screenshot -a $app.ProcessId -o smoke-test.png
Windows developer