Benutzeroberflächenautomatisierung

Überprüfen und interagieren Sie mit der Ausführung von Windows Anwendungen über die Befehlszeile. Wird von KI-Agents und Entwicklern für Benutzeroberflächentests, Debugging und Automatisierung verwendet.

Übersicht

winapp ui bietet Befehle zum Überprüfen und Interagieren mit Windows App-UIs. Verwendet Windows Benutzeroberflächenautomatisierung (UIA). Funktioniert mit jeder Windows-App – WPF, WinForms, Win32, Electron und WinUI 3. Die meisten Befehle steuern die App über UIA-Muster (keine Eingabeeinfügung); ui click ist die Ausnahme und verwendet echte Maussimulation für Steuerelemente, die nicht unterstützt InvokePatternwerden.

Quick Start

# 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

Ziel-Apps

Nach Prozessname

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

Nach Fenstertitel

winapp ui inspect -a "LICENSE - Notepad"
winapp ui inspect -a "Fix WinApp"     # partial title match

Von PID

winapp ui inspect -a 12345

Von HWND (stabil – übersteht Tabstopp-/Titeländerungen)

# 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

Wird -a für die Ermittlung verwendet, -w um eine stabile Zielbestimmung zu bieten. Wenn -a sie mehreren Fenstern entsprechen, listet der Befehl sie mit HWNDs auf, die Sie auswählen können.

Selektoren

Zielelemente mithilfe der Auswahl, die in [brackets] der Untersuchungs-/Suchausgabe angezeigt wird. Es gibt drei Arten von Selektoren:

Selektor Bedeutung Example
MinimizeButton AutomationId (angezeigt, wenn eindeutig – stabil, bevorzugt) winapp ui invoke MinimizeButton -a myapp
btn-close-d1a0 Semantischer Strich (wird angezeigt, wenn keine eindeutige Automatisierungs-ID vorhanden ist) winapp ui invoke btn-close-d1a0 -a myapp
Submit Nur-Text-Suche mit Name/AutomationId (Teilzeichenfolge ohne Groß-/Kleinschreibung) winapp ui invoke Submit -a myapp

AutomationId-Selektoren sind Entwickler-Set-IDs (AutomationProperties.AutomationId in XAML). Wenn eine AutomationId in der gesamten UI-Struktur inspect eindeutig ist und search sie direkt als Selektor anzeigen – diese überleben Layoutänderungen, Lokalisierung und Strukturumstrukturierung.

Slug-Selektoren (z. B. ) werden generiert, btn-close-d1a0wenn keine eindeutige AutomationId vorhanden ist. Format: prefix-name-hash. Der Hash überprüft die Elementidentität, kann aber nach Ui-Änderungen veraltet sein.

Prüfen des Ausgabeformats

Der inspect Befehl zeigt die Elementstruktur mit farbiger Ausgabe an (Selektor in Cyan, Name in Grün, Metadaten in Grau):

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

Das erste Wort in jeder Zeile ist die Selektor – verwenden Sie es mit anderen ui Befehlen. Wenn ein Element über eine eindeutige AutomationId verfügt, wird es direkt verwendet (z. B TabView. , NewTabButton). Wenn keine eindeutige AutomationId vorhanden ist, wird eine generierte Slug verwendet (z. B. tab-newtab-5f5b).

Semantische Striche

Slugs verwenden das Format: dabei: prefix-normalizedname-hash

  • Präfix — Abkürzung vom Typ 3 Buchstaben (btn, txt, chk, cmb, itm, tab, img, lbl, pn, win, grp, lnk, mnu usw.)
  • normalisierter Name – alphanumerisch in Kleinbuchstaben von AutomationId (bevorzugt) oder Name, max. 15 Zeichen
  • Hash – 4-Zeichen-Hex-Hash der RuntimeId des Elements (validates element identity)

Slugs sind shellsicher (keine Sonderzeichen), eindeutig und können direkt als Argumente verwendet werden. Der Hash stellt die Veraltetkeitserkennung bereit – wenn das Element ersetzt wurde, erhalten Sie Folgendes: "Element hat sich möglicherweise geändert. Prüfung erneut ausführen."

Elemente ohne Namen oder AutomationId zeigen nur Präfix + Hash an (z. B. pn-c8a3).

Mehrdeutige Übereinstimmungen

Slugs from inspect/search output are unique, but can change across layout changes - use them over plain type names or text when multiple matches. Wenn eine Auswahl mehrdeutig ist, druckt die CLI alle Übereinstimmungen mit ihren Slugs, sodass Sie das richtige auswählen und mit diesem Slug erneut ausführen können.

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

Verwenden Sie Nur-Text, um nach Elementen zu suchen – keine spezielle Syntax erforderlich:

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

Wenn eine Textsuche mehreren Elementen entspricht (z. B. SettingsExpander, wobei "Group", "Button" und "Text" denselben Namen aufweisen), wählt die CLI automatisch das einzige aufrufende Element aus. Wenn mehrere aufrufen können, werden alle Übereinstimmungen mit Slugs aufgelistet.

Bei nicht aufrufenden Suchergebnissen (z. B. einem TextBlock innerhalb einer Schaltfläche) zeigt die Suche automatisch den nächsten aufrufenden Vorgänger an – das übergeordnete Element, mit invokedem Sie arbeiten können. Dies funktioniert für alle Suchmarkierer:

  lbl-savechanges-a1b2 "Save changes" (120,40 80x20)
        ^ invoke via: btn-save-c3d4 "Save"

Die oberflächenierte Selektor kann direkt verwendet werden:

winapp ui invoke btn-save-c3d4 -a myapp    # invoke the parent Button

Commands

status

Stellen Sie eine Verbindung mit einer App her, und zeigen Sie Verbindungsinformationen an.

winapp ui status -a notepad
winapp ui status -a notepad --json

Überprüfen

Zeigen Sie die Ui-Elementstruktur an. Die Ausgabe zeigt semantische Schrägstriche mit zwei Leerzeichen für die Hierarchie an:

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

Beispielausgabe (Standard):

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)

Beispielausgabe (--interactive — nur aufrufende Elemente, flache Liste):

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)

Elemente können diese Zustandsmarkierungen anzeigen:

  • [on] / [off] / [indeterminate] — Umschalten/Kontrollkästchenstatus
  • [collapsed] / [expanded] — Erweitern/Reduzieren des Zustands für Bäume, Kombinationsfelder, Menüelemente
  • [scroll:v] / [scroll:h] / [scroll:vh] — Bildlaufcontainer (vertikal, horizontal oder beides)
  • [offscreen] — Element ist auf dem Bildschirm nicht sichtbar
  • [disabled] — Element ist nicht aktiviert
  • value="..." — aktueller Textinhalt für bearbeitbare Elemente (bei unterschiedlichem Namen)

Suchen Sie Elemente, die mit einer Auswahl übereinstimmen. Ausgabe zeigt semantische Slugs:

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

Beispielausgabe:

  btn-minimize-d1a0 "Minimize" (1222,206 48x48)
  btn-maximize-e2b1 "Maximize" (1270,206 48x48)
  btn-close-d1a2 "Close" (1318,206 48x48)

Slugs, die in der Ausgabe (z. B. ) angezeigt werden, btn-minimize-d1a0können direkt mit anderen Befehlen verwendet werden:

winapp ui invoke btn-minimize-d1a0 -a notepad

get-property

Dient zum Lesen von Eigenschaftswerten aus einem Element. Enthält musterspezifischen Zustand (ToggleState, Value, IsSelected usw.).

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

Erfassen Sie ein Fenster oder Element als PNG. Wenn mehrere Fenster vorhanden sind (z. B. Das Dialogfeld "App + Öffnen"), werden sie in einem einzelnen PNG-Element mit jedem Fenster zusammengesetzt.

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)

Wenn Dialogfelder oder Popups geöffnet sind, werden alle Fenster in einem PNG zusammengesetzt, sodass Sie den vollständigen UI-Zustand in einem einzelnen Bild sehen können.

Verwenden Sie diese Option --capture-screen , wenn Sie Popupmenüs, Dropdowns, Flyouts oder QuickInfo-Überlagerungen erfassen müssen. Im --capture-screen Modus (und beim Wiederholen, nachdem ein leerer Frame erkannt wurde) wird das Zielfenster zuerst in den Vordergrund verschoben. Normale Fensteraufnahmen verschieben das Fenster nicht.

Aufrufen

Programmgesteuertes Aktivieren eines Elements (Klickschaltfläche, Kontrollkästchen umschalten, Kombinationsfeld erweitern).

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

Versucht Muster in der Reihenfolge: InvokePattern → TogglePattern → SelectionItemPattern → ExpandCollapsePattern.

klicken

Klicken Sie mit der Maussimulation auf ein Element an seinen Bildschirmkoordinaten. Verwenden Sie diese Option für Steuerelemente, die nicht unterstützt InvokePattern werden (z. B. Spaltenüberschriften, Listenelemente).

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

Festlegen eines Werts für ein bearbeitbares Element (Text für TextBox/ComboBox, Zahl für Schieberegler).

winapp ui set-value txt-textbox-a4b1 "Hello world" -a notepad
winapp ui set-value sld-volume-b2c3 75 -a myapp

get-value

Lesen Sie den aktuellen Wert aus einem Element. Verwendet eine intelligente Fallbackkette: TextPattern (RichEditBox, Document) → ValuePattern (TextBox, Slider) → SelectionPattern (ComboBox, RadioButton, TabView) → Name (Bezeichnungen).

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": "..." }

Fokus

Verschieben des Tastaturfokus auf ein Element.

winapp ui focus txt-textbox-a4b1 -a notepad

Scrollen in die Ansicht

Scrollen Sie ein Element in den sichtbaren Bereich.

winapp ui scroll-into-view itm-targetitem-c3d4 -a myapp

Warten auf

Warten Sie, bis ein Element angezeigt, ausgeblendet oder ein Wert ein Ziel erreicht hat.

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

Blättern

Scrollen Sie in einem Containerelement. Suchen Sie bildlauffähige Container mit search scroll – suchen Sie nach [scroll:v] (vertikalen) oder [scroll:h] (horizontalen) Markierungen.

# 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

get-focused

Zeigt das Element an, das derzeit den Tastaturfokus besitzt.

winapp ui get-focused -a myapp

Listenfenster

Auflisten aller sichtbaren Fenster für eine App, einschließlich Popups und Dialogfeldern.

winapp ui list-windows -a imageresizer
winapp ui list-windows -a Terminal
winapp ui list-windows                                      # all windows (no filter)

Frameworkunterstützung

Rahmenwerk Überprüfen search Aufrufen Set-Value Screenshot
WPF ✅ Vollständige Struktur ✅ Alle Eigenschaften ✅ Alle Muster
WinForms
Win32
WinUI 3
Elektron ⚠– Chromium-Struktur ⚠️ Begrenzt ⚠– Variiert ⚠– Variiert
Flutter ⚠️ Einfach ⚠️ Einfach ❌ Minimal

Problembehandlung

Error Ursache Lösung
"Keine ausgeführte App gefunden" Nicht ausgeführte App oder nicht übereinstimmende Namen Überprüfen des Prozessnamens oder Verwenden von PID
"Mehrere Fenster übereinstimmen" Mehrdeutiger -a Wert Verwenden -w <HWND> aus den aufgelisteten Optionen
"hat mehrere Fenster" Der Prozess verfügt über mehrere Fenster Wird -w <HWND> verwendet, um bestimmte Zielwert zu erreichen.
"Selector matched N elements" Mehrdeutige Legacyauswahl Verwenden von Schrägstrichen aus der inspect Ausgabe oder Anfüge [0][1] an Legacyselektoren
"Element hat sich möglicherweise geändert" Der Slug-Hash stimmt nicht mit dem aktuellen Element überein. Erneut ausführen inspect oder search neue Slugs erhalten
"unterstützt kein Aufrufmuster" Element kann nicht aufgerufen werden Verwenden sie inspect für das Element, um ein bestimmbares untergeordnetes Element zu finden
"Es wurde kein UIA-Fenster gefunden" UIA kann den Prozess nicht sehen Wird list-windows verwendet, um den HWND zu finden, und dann -w
"Fenster hat null Größe" Fenster wird minimiert Die App wird automatisch wiederhergestellt.
Popup/Dropdown nicht im Screenshot PrintWindow erfasst keine Überlagerungen Kennzeichnung verwenden --capture-screen

Allgemeine Muster

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

Suchen von Text und Aufrufen des übergeordneten Elements

# 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

Mehrdeutigkeit von doppelten Elementen

winapp ui search '#Image' -a myapp; winapp ui invoke itm-image-a2b3 -a myapp

Screenshot mit Popupüberlagerungen

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

Entdecken, Klicken und Überprüfen

winapp ui inspect -a myapp --interactive; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp

Interaktion des Dateidialogfelds

Dialogfelder zum Öffnen/Speichern von Dateien sind Standardmäßige dialogfelder Windows mit UIA-Unterstützung:

# 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>

Wird verwendet inspect -w <dialog-hwnd> --interactive , um die tatsächlichen Slugs für ein bestimmtes Dialogfeld zu ermitteln.

Gründe ; für Verkettung (nicht &&)

Der Operator von && PowerShell kann fixieren, wenn eine systemeigene CLI in Stderr schreibt oder ANSI-Escapesequenzen verwendet. Verwenden Sie ; stattdessen – sie führt jeden Befehl bedingungslos aus und vermeidet diesen Deadlock. Dies ist auch für Agentworkflows besser: In der Regel soll der Screenshot ausgeführt werden, auch wenn der Aufruf einen Nicht-Null-Exit hatte.

CI-Testmuster

Verwenden Sie winapp ui-Befehle in CI-Pipelines (GitHub Actions, Azure DevOps) für Rauchtests und ui-Überprüfungen. wait-for mit --property und --value fungiert als Assertion – es gibt Exitcode 1 beim Timeout zurück, und der CI-Schritt wird automatisch nicht ausgeführt.

Starten und Testen in 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

Assert-Elementstatus mit wait-for

wait-for --value abruft, bis der Wert eines Elements mit der erwarteten Zeichenfolge übereinstimmt, wobei derselbe intelligente Fallback verwendet get-value wird (TextPattern → ValuePattern → SelectionPattern → Name). Gibt exit code 0 on match, exit code 1 on timeout - making it a CI-friendly assertion. Verwenden Sie --property diese Option, um stattdessen eine bestimmte UIA-Eigenschaft zu überprüfen.

# 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

Bestätigen mit JSON-Ausgabe

Verwendung --json mit PowerShell oder jq für komplexere Assertionen:

Exit-Code-Vertrag für search und wait-for im --json Modus: Wenn kein Element übereinstimmt (search) oder das Wartezeitlimit (wait-for), schreibt der Befehl einen vollständig analysierten Ergebnisumschlag in stdout ({ "matchCount": 0, ... } oder { "found": false, "timedOut": true, ... }) und gibt den Ausgangscode 1 zurück. Stderr ist im --json Modus leer (Loggerausgabe wird unterdrückt). Verzweigung auf den Umschlagfeldern oder von $LASTEXITCODE, je nachdem, welche ergonomischer ist.

# 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)" }

Vollständiges Rauchtestbeispiel

# 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