Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Sprawdzanie i interakcja z uruchamianiem aplikacji Windows z poziomu wiersza polecenia. Używane przez agentów sztucznej inteligencji i deweloperów do testowania, debugowania i automatyzacji interfejsu użytkownika.
Przegląd
winapp ui udostępnia polecenia do inspekcji i interakcji z interfejsami użytkownika aplikacji Windows.
Używa Windows automatyzacja interfejsu użytkownika (UIA). Współpracuje z dowolną aplikacją Windows — WPF, WinForms, Win32, Electron i WinUI 3.
Większość poleceń napędza aplikację za pomocą wzorców interfejsu użytkownika (bez iniekcji danych wejściowych). Wyjątki wprowadzają rzeczywiste dane wejściowe: ui click/ui hover/ui dragużywaj symulacji myszy,ui touch/ui pen syntetyzują dane wejściowe dotyku i pióra/rysika oraz ui send-keys syntetyzują dane wejściowe klawiatury — w przypadku kontrolek i scenariuszy, których wzorce UIA nie mogą prowadzić.
Important
Wymaganie dotyczące pulpitu interakcyjnego (czasowniki iniekcji danych wejściowych).click, hover, dragtouch, pen, scroll --wheeli send-keys --via send-input syntetyzują dane wejściowe na poziomie systemu operacyjnego, więc potrzebują odblokowanego, interaktywnego pulpitu z oknem docelowym na pierwszym planie. Na zablokowanej stacji roboczej lub bezpiecznym pulpicie (LogonUI/UAC) nie mogą wstrzyknąć i szybko zakończyć się niepowodzeniem no_interactive_desktop (różni się od podniesienia uprawnień/foreground_not_target przypadków).
touch
/
penponadto odrzucanie, gdy okno nie zostanie rozpoznane (no_target); współrzędna poza oknem docelowym jest ostrzeżeniem niekrytycznym (wpis w warnings[]trybie tekstowym lub wierszem ostrzegawczym) i iniekcja jest nadal kontynuowana — --json zgodnie z czasownikami myszy. Wszystkie inne elementy — inspect, get-propertyset-valuesearchwait-forinvokeget-value, scroll --direction/--to — napędzają aplikację za pomocą wzorców interfejsu użytkownika i są przyjazne dla sesji bezgłówkowych/zablokowanych.
screenshot jest wyjątkiem między czasownikami niewstrzykania: ma wyłączny obrót, a jego przechwytywanie może wymagać interaktywnego pulpitu, ponieważ aparat przywraca zminimalizowany cel i wraca do pierwszego planu, gdy przechwytywanie ramki jest niedostępne lub --capture-screen jest używane. Preferuj czasowniki wzorca UIA w ciągłej integracji; zarezerwować czasowniki iniekcji dla scenariuszy, które naprawdę potrzebują rzeczywistych danych wejściowych. Przed wstrzyknięciem czasowniki gestu również ponownie rozpoznają element docelowy i odmówić, target_moved jeśli nadal animuje/przenosi się, zamiast lądować dane wejściowe w pustym miejscu.
Szybki 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
Uruchamianie automatyzacji interfejsu użytkownika w piaskownicy Windows
Aby zachować automatyzację poza pulpitem, dodaj --on sandbox do poleceń uruchamiania i interfejsu użytkownika:
winapp run . --on sandbox --detach
winapp ui inspect --on sandbox -a MyApp
winapp ui invoke --on sandbox SubmitButton -a MyApp
--detach zwraca po uruchomieniu; bez niego run czeka na zakończenie działania aplikacji. Zachowaj --on sandbox przy każdym poleceniu gościa, w tym przy użyciu identyfikatora PID lub uchwytu okna.
Zobacz Windows Wykonywanie piaskownicy, aby uzyskać informacje o wymaganiach klienta, krótkiej konfiguracji/ponownym połączeniu zmian fokusu, koordynacji przepływu pracy i dostarczaniu danych wyjściowych hosta.
Zapytania o określonym zakresie i wpisane
winapp ui search "Welcome to MyApp" -a myapp --root MailRow --type Text --class-name TextBlock
winapp ui get-value Subject -w 123456 --root MailRow --type TextBox
winapp ui get-property Subject -a myapp --root MailRow --type Edit --property Value
winapp ui wait-for Subject -a myapp --root MailRow --type Edit --value "Ready" --timeout 10000
search, , get-propertyget-valuei wait-for zaakceptuj te opcjonalne filtry.
Selektor i każdy podany filtr muszą być zgodne z tym samym elementem:
-
--root <selector>wyszukuje tylko elementy potomne jednego unikatowo pasującego katalogu głównego, nigdy samego katalogu głównego. Użyj identyfikatora AutomationId lub slug zinspect, aby uściślić. Katalog główny, który pasuje do wielu elementów, kończy się niepowodzeniem,ambiguous_selectornawet jeśli jedno dopasowanie jest wywoływane. Brak katalogu głównego nie generuje dopasowań. Po znalezieniu katalogu głównego zapytania nie przeszukuje niepowiązanych okien podręcznych, nawet jeśli element potomny nie pasuje. Zapytania nie są ograniczone przezinspectgłębokość wyświetlania. -
--type <control-type>pasuje do typu kontrolki UIA, ignorując wielkość liter. Jedynymi aliasami sąTextBox→EditiTextBlock→Text. Nieznane nazwy (w tym identyfikatory liczbowe i wyrażenia wieloznaczne) kończą się niepowodzeniem.invalid_arguments -
--class-name <literal>pasuje do całegoClassNameinterfejsu użytkownika dostawcy, ignorując przypadek. Nie jest to podciąg, symbol wieloznaczny ani wyrażenie regularne. Użyjget-property --property ClassNamepolecenia , aby odnaleźć wartość dostawcy. Nazwa klasy nie musi być równa typowi kontrolki UIA.
Filtrowane zapytania korzystają z widoku kontrolki UIA, w tym samym widoku wyświetlanym przez inspectprogram .
Węzły dostawcy uwidocznione tylko w widoku nieprzetworzonym nie są zwracane; użyj inspect polecenia , aby znaleźć kontrolkę zawierającą i jej selektor.
Obsługiwane są wszystkie 41 typów oficjalnych: Button, , Calendar, CheckBoxDataGridImageDataItemListItemDocumentListSplitButtonMenuAppBarMenuItemSemanticZoomProgressBarSeparatorMenuBarTitleBarRadioButtonHyperlinkCustomScrollBarGroupSliderThumbHeaderItemTreeItemTableHeaderPaneWindowTreeToolTipToolBarTextTabSpinnerTabItemComboBoxEditStatusBar.
wait-for ponownie rozpoznaje selektor główny w każdym ankiecie, więc katalog główny może pojawić się po uruchomieniu polecenia. W przypadku --goneelementu , brak katalogu głównego oznacza, że nie ma pasującego elementu potomnego; niejednoznaczny katalog główny jest błędem, a nie powodzeniem.
Przerwane wyszukiwanie nie jest dowodem na zniknięcie: jeśli element zostanie usunięty podczas wyszukiwania lub zastąpiony przed odczytem --value , następne sprawdzanie sondy ponownie; inne wyszukiwanie lub błędy odczytu kończą się niepowodzeniem.
-w <HWND> Ogranicza odnajdywanie główne do drzewa UIA tego okna. Za pomocą -apolecenia odnajdywanie główne może również znajdować okna podręczne aplikacji. Dokładne dopasowania root AutomationId mają pierwszeństwo przed dopasowaniami podciągów we wszystkich tych oknach; wiele dokładnych dopasowań nadal kończy się niepowodzeniem z .ambiguous_selector
Element główny wybiera ten element nawet wtedy, gdy inne okno ma ten sam identyfikator AutomationId. Jeśli wybrany katalog główny zostanie zastąpiony, jego stary ślimak nie jest już zgodny; użyj identyfikatora AutomationId lub katalogu głównego nazwy, jeśli chcesz sondować, aby wykonać zamianę.
Gdy filtry są obecne, polecenia odczytujące pojedynczy element kończą się niepowodzeniem ambiguous_selector , jeśli pozostanie więcej niż jeden element; zawęzi filtry lub użyj unikatowego ślimaka. Dokładne dopasowania AutomationId zachowują pierwszeństwo przed dopasowaniami podciągów w ramach filtrowanego zakresu. Pominięcie wszystkich trzech opcji zachowuje istniejące zachowanie zapytania.
Koordynowanie współbieżnych przepływów pracy interfejsu użytkownika
Windows ma tylko jedno okno pierwszego planu, fokus klawiatury, jeden kursor i jeden strumień wejściowy. Gdy dwa winapp ui przepływy pracy działają na tym samym zalogowanym pulpicie jednocześnie, mogą ukraść fokus ze sobą, odrzucić menu, które zostało otwarte po prostu lub przenieść element docelowy z poniżej oczekującego kliknięcia.
Arbitraż jest zawsze włączony. Każde winapp ui polecenie, które dotyka pulpitu fizycznego, ma kolei, bez konfiguracji i nie ma sposobu, aby wyłączyć go, więc dwóch agentów nigdy nie może wpisać do siebie okien. Polecenia tylko do odczytu działają współbieżnie.
Ciągłość między poleceniami jest włączona. Domyślnie każde polecenie jest samodzielnym jednym strzałem: czeka na jego kolei, wykonuje swoją pracę i natychmiast zwalnia pulpit. Aby zachować pulpit w kilku poleceniach, nadaj im wszystkie te same identyfikatory przepływu pracy:
# Set once per logical UI workflow
$env:WINAPP_UI_WORKFLOW_ID = [guid]::NewGuid().ToString()
Co musisz wiedzieć:
- Identyfikator przepływu pracy nazywa jeden logiczny przepływ pracy — niekoniecznie cały agent, a niekoniecznie jedną aplikację. Użyj tej samej wartości dla współpracujących poleceń (nagranie i kliknięcia powinny zostać przechwycone); użyj różnych wartości dla niezależnych przepływów pracy, nawet jeśli jeden agent uruchamia oba te przepływy pracy.
- Bez identyfikatora każde polecenie jest niezależnym jednorazowym strzałem. To nadal arbitrates, ale bank nie ma łaski i ręce pulpitu od momentu, w którym się kończy. Dwa polecenia no-id są oddzielnymi przepływami pracy nawet w przypadku uruchamiania z tej samej powłoki.
- Hosty fresh-shell i adaptacyjne muszą wstrzyknąć tę samą wartość. Jeśli każde polecenie jest uruchamiane w nowej powłoce — w jaki sposób większość narzędzi agenta wywołuje pracę — jedyną rzeczą, która może je zgrupować, jest jawnie
WINAPP_UI_WORKFLOW_IDprzekazana do każdego współpracującego wywołania. - Czterosekundowa grace chroni napięte wybuchy, a nie rozumowanie modelu. Przepływ pracy o identyfikatorze zachowuje swój obrót tak długo, jak następne polecenie zostanie uruchomione w ciągu czterech sekund. Obejmuje to polecenia back-to-back w jednym skrycie; celowo wygasa, gdy model myśli. Jest to powrót, gdy nie można powiedzieć, że skończysz — kiedy możesz, uruchom
winapp ui yieldzamiast czekać. - Adaptacyjne przepływy pracy muszą być ponownie wymagane, odświeżone i odtwarzane. Po przerwie w rozumowaniu inny przepływ pracy mógł używać pulpitu, więc otwórz ponownie menu, ponownie rozwiąż element, a następnie wykonaj działania. Wysyłaj znane kompleksowe sekwencje jako jeden ciasny skrypt, a nie trzymając pulpitu, gdy myślisz.
- Kolejność jest najpierw koligacją właściciela, a następnie FIFO między innymi. Chociaż przepływ pracy jest aktywny lub wewnątrz jego łaski może nadal wydawać polecenia, nawet jeśli inne przepływy pracy już czekają. Po jego uruchomieniu
winapp ui yieldlub wygaśnięciu prolongaty oczekujące przepływy pracy są obsługiwane w ścisłej kolejności przyjazdu. Ciągłe działanie przez jeden przepływ pracy może w związku z tym opóźnić inne w nieskończoność. - Nie ma twardej czapki. Długi skrypt, niezwiązane nagranie lub pętla awarii może blokować inne przepływy pracy mutujące.
-
Anulowanie lub zakończenie procesu to odzyskiwanie zablokowanego przepływu pracy na żywo. Oczekujące polecenia wyświetlają stan po jednej sekundzie i można je zatrzymać za pomocą
Ctrl+Cpolecenia , który kończy działanie130. - Współpracują tylko zgodne zaktualizowane pliki binarne. Starsze
winappkompilacje poprzedzają tę funkcję i nie są koordynowane. Kod wywołujący pakiety NuGet automatyzacja interfejsu użytkownika bezpośrednio znajduje się poza tą gwarancją całkowicie — koordynacja znajduje się w interfejsie wiersza polecenia, a nie w pakietach.
Które polecenia czekają na kolei:
| Behavior | Commands |
|---|---|
| Przebiegi współbieżnie (nigdy nie czekaj) |
status, list-windows, inspect, search, , get-property, get-value, get-focusedwait-for |
| Czeka na kolei, ale nigdy nie zabiera pulpitu |
set-value, , scroll-into-view, , scroll --direction/--torecord |
| Czeka na kolei i zabiera pulpit wyłącznie |
invoke, click, drag, hover, scroll --wheel, touch, pen, focus, send-keys, screenshot |
Środkowy wiersz jest tym, który warto zrozumieć.
set-value
scroll-into-view, i scroll --direction/--to dyski UIA wzorce, a nie pierwszy plan, więc pozostają bezgłówne/zablokowane sesji przyjazne i nigdy nie blokują nikogo z korzystania z pulpitu. Jednak zmieniają to , co pokazuje aplikacja, więc oczekują za kolei innego przepływu pracy, a nie edytują pola lub przewijają listę z poziomu pod kliknięciem innej osoby.
W ramach jednego przepływu pracy nakładają się one na inną pracę udostępnioną — w jaki sposób record przechwytuje set-value ona wywołania, które rejestruje.
Nie ignorują bariery przesyłania dalej własnego przepływu pracy: wcześniejsze DesktopExclusive polecenie tego samego przepływu pracy (a click, a screenshot) nadal blokuje je, dokładnie tak, jak blokuje każde późniejsze polecenie, więc kliknięcie i mutacja, która następuje, pozostają w kolejności, w której je napisali.
screenshot zawsze kolejkuje do wyłącznego kolei. Nie każde przechwytywanie zakłóca pulpit — zwykłe widoczne okno przechwycone za pośrednictwem Windows przechwytywania grafiki nie — ale aparat przywraca cel, jeśli jest zminimalizowany, i wraca do pierwszego planu, gdy przechwytywanie ramki jest niedostępne lub --capture-screen odczytuje ekran na żywo. Te potrzeby wymagają tylko powierzchni, gdy przechwytywanie jest w toku, więc polecenie przyjmuje się z góry, a nie zgadywanie. Gdy składa się kilka okien, przechwytuje je wszystkie pod jednym wyłącznym kolei, więc zapisany obraz jest jednym spójnym momentem, a nie mieszanką przed i po. Kodowanie i zapisywanie pliku odbywa się po wydaniu pulpitu.
--capture-screenpotrzebuje dokładnie jednego okna. Przechwytywanie ekranu na żywo rejestruje wszystkie elementy rzeczywiście z przodu, a tylko jedno okno może być. Jawne wybranie okna daje-w <hwnd>mu dokładnie jeden region — piksele wewnątrz granic tego okna, w tym dowolne okno dialogowe lub nakładka widocznie na jego górze, co jest powodem do odczytania ekranu w pierwszej kolejności. Gdy-adopasuje kilka okien najwyższego poziomu lub posiadanych okien, nie ma takiego wyboru, więc polecenie kończy się niepowodzenieminvalid_argumentsprzed przechwyceniem niczego, a nie walką na pierwszym planie. Uruchomwinapp ui list-windows -a <app>polecenie i ponów próbę za pomocą-w <hwnd>polecenia lub upuść--capture-screen, aby utworzyć złożone okno z własnej zawartości.
record udostępnia swoją kolej, dzięki czemu dane wejściowe tego samego przepływu pracy mogą przeplatać się z przechwytywaniem — w ten sposób rejestrujesz przepływ pracy kierujący aplikacją. Dwa zastrzeżenia:
- Identyfikator
recordprzepływu pracy bez identyfikatora jest właścicielem jednorazowego, więc blokuje każdy inny przepływ pracy przez cały czas trwania. Aby zarejestrować i kliknąć w tym samym czasie, nadaj obu poleceniam to samoWINAPP_UI_WORKFLOW_ID. - Na hoście bez obsługi przechwytywania ramek nagrywanie wraca do printWindow, którego odzyskiwanie pustej ramki może na pierwszym planie okna w każdej chwili. Na pulpicie znajduje się całe nagranie, a polecenie mówi tak w danych wyjściowych; nawet dane wejściowe tego samego przepływu pracy będą czekać.
Błędy, które mogą zostać wyświetlone: invalid_ui_workflow_id (zmienna jest ustawiona, ale pusta lub ponad 256 znaków), desktop_coordination_unavailable (stan koordynacji jest nieczytelny i nie można bezpiecznie skompilować lub został zapisany przez nowsze winappqueue_capacity_exceeded ), (64 polecenia z innych przepływów pracy już czekają — limity żyją zagranicznych kelnerów, a nie uruchomione procesy, więc wpisy należące do poleceń, które zakończyły lub zostały zabite, nie zajmują miejsca, i własne polecenia przepływu pracy kolejki za sobą, a nie względem tego limitu), ui_turn_busy ( podczas gdy twój własny przepływ pracy nadal ma uruchomione polecenie), i cancelled (Ctrl+C podczas oczekiwania, kod zakończenia 130yield ).
Zwalnianie kolei wcześnie: winapp ui yield
Czterosekundowa łaska jest rezerwą: utrzymuje pulpit zarezerwowany, gdy nie można powiedzieć, że skończysz. Kiedy możesz to powiedzieć, powiedzmy tak — yield przekazuje pulpit natychmiast, zamiast sprawiać, że wszyscy inni czekają na łaskę, której nikt nie potrzebuje.
$env:WINAPP_UI_WORKFLOW_ID = [guid]::NewGuid().ToString()
winapp ui invoke File -a notepad
winapp ui click "Save As..." -a notepad
winapp ui set-value txt-filename-a1b2 "notes.txt" -a notepad
winapp ui yield # done — a waiting workflow starts now, not in four seconds
- Polecenia jednorazowe nie powinny w ogóle ustawiać identyfikatora przepływu pracy. Bez jednego każde polecenie zwalnia już pulpit w momencie jego zakończenia i nie ma nic do uzyskania.
- Przepływy pracy wieloetapowe powinny być zwracane po zakończeniu, zwłaszcza gdy inne przepływy pracy mogą czekać. Kosztuje jedno szybkie polecenie i usuwa czterosekundowe stoisko od wszystkich innych.
- Jest idempotentny. Zwracanie dwa razy lub po wygaśnięciu łaski już kończy się powodzeniem i raportami
{ "released": false }— jest to normalny koniec skryptu, a nie awaria. - Nigdy nie zwalnia innego przepływu pracy. Jeśli ktoś inny przechowuje pulpit lub nikt nie robi, jest to no-op.
- Kończy się to niepowodzeniem,
ui_turn_busyjeśli twój własny przepływ pracy nadal ma uruchomione polecenie lub jest w kolejce — nagranie, powiedzmy. Zwalnianie pod spodem, które przekaże pulpit do środka polecenia, więc nic nie jest zwalniane i uruchomione polecenie nie ma wpływu. Poczekaj na to lub zatrzymaj go, a następnie powróć. -
WINAPP_UI_WORKFLOW_IDWymaga . Bez jednego kończy się niepowodzeniem z .invalid_arguments - Brak aplikacji i selektora: zwraca rezerwację, a nie okno, więc nadal działa po zamknięciu aplikacji.
Oczekujące polecenie jest obudzony przez każdego, kto zwalnia pulpit, a nie sondując go, więc kolejka kosztuje prawie nic, gdy czeka i przekazanie jest natychmiastowe. Każdy kelner sprawdza się również samodzielnie od czasu do czasu, co powoduje odzyskanie pulpitu, gdy proces zostanie zabity i nigdy nie publikuje niczego: polecenie na czele kolejki wygląda co pół sekundy, a polecenia za nim — które nie mogą być uruchamiane przed głową i tak — co kilka sekund.
Określanie wartości docelowej aplikacji
Według nazwy procesu
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
Według tytułu okna
winapp ui inspect -a "LICENSE - Notepad"
winapp ui inspect -a "Fix WinApp" # partial title match
Według identyfikatora PID
winapp ui inspect -a 12345
Przez HWND (stabilny — przetrwa zmiany tabulatora/tytułu)
# 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
Służy -a do odnajdywania w -w celu uzyskania stabilnego określania wartości docelowej. Gdy -a pasuje do wielu okien, polecenie wyświetla je z HWNDs, które chcesz wybrać.
Selektory
Elementy docelowe przy użyciu selektora wyświetlanego w [brackets] danych wyjściowych inspekcji/wyszukiwania.
Istnieją trzy typy selektorów:
| Selector | Znaczenie | Example |
|---|---|---|
MinimizeButton |
AutomationId (wyświetlany, gdy unikatowy — stabilny, preferowany) | winapp ui invoke MinimizeButton -a myapp |
btn-close-d1a0 |
Slug semantyczny (pokazany, gdy nie ma unikatowego identyfikatora AutomationId) | winapp ui invoke btn-close-d1a0 -a myapp |
Submit |
Wyszukiwanie w postaci zwykłego tekstu względem parametru Name/AutomationId (podciąg bez uwzględniania wielkości liter) | winapp ui invoke Submit -a myapp |
Selektory AutomationId to identyfikatory zestawu deweloperów (AutomationProperties.AutomationId w języku XAML).
Gdy identyfikator AutomationId jest unikatowy w całym drzewie interfejsu inspect użytkownika i search wyświetla go bezpośrednio jako selektor — te zmiany układu przetrwania, lokalizacja i restrukturyzacja drzewa.
Selektory Slug (np. ) są generowane, btn-close-d1a0gdy nie istnieje unikatowy identyfikator AutomationId.
Format: prefix-name-hash. Skrót weryfikuje tożsamość elementu, ale może przestarzał po zmianie interfejsu użytkownika.
Sprawdzanie formatu danych wyjściowych
Polecenie inspect wyświetla drzewo elementów z kolorowymi danymi wyjściowymi (selektor w cyjanku, nazwa na zielono, metadane w kolorze szarym):
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
Pierwszym wyrazem w każdym wierszu jest selektor — użyj go z innymi ui poleceniami.
Gdy element ma unikatowy identyfikator AutomationId, jest używany bezpośrednio (np. TabView, NewTabButton).
Jeśli nie istnieje unikatowy identyfikator AutomationId, jest używany wygenerowany ślimak (np. tab-newtab-5f5b).
Slugi semantyczne
Slugi używają formatu: prefix-normalizedname-hash gdzie:
- prefiks — 3-literowy skrót typu (btn, txt, chk, cmb, itm, tab, img, lbl, pn, win, grp, lnk, mnu itp.)
- normalizedname — małe litery alfanumeryczne z identyfikatora AutomationId (preferowane) lub nazwa, maksymalnie 15 znaków
- hash — skrót 4-znakowy skrót szesnastkowy identyfikatora RuntimeId elementu (weryfikuje tożsamość elementu)
Slugi są bezpieczne za pomocą powłoki (bez znaków specjalnych), unikatowe i mogą być używane bezpośrednio jako argumenty. Bez filtrów zapytań skrót zapewnia wykrywanie nieaktualności — jeśli element został zastąpiony, otrzymasz: "Element mógł ulec zmianie. Uruchom ponownie inspekcję". Aby uzyskać informacje o filtrowanych zapytaniach, zobacz Zapytania o zakresie i wpisywane.
Elementy bez nazwy lub AutomationId pokazują tylko prefiks + skrót (np. pn-c8a3).
Uściślanie wielu dopasowań
Slugi z inspect/search danych wyjściowych są unikatowe, ale mogą zmieniać się między zmianami układu — używają ich w zwykłych nazwach typów lub tekście, gdy wiele dopasowań. Gdy selektor jest niejednoznaczny, interfejs wiersza polecenia drukuje wszystkie dopasowania ze swoimi ślimakami, aby można było wybrać właściwy i ponownie uruchomić z tym ślimakiem.
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
Wyszukiwanie zwykłego tekstu
Użyj zwykłego tekstu, aby wyszukać elementy — nie jest wymagana żadna specjalna składnia:
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
Gdy wyszukiwanie tekstu pasuje do wielu elementów (np. SettingsExpander, gdzie grupa, przycisk i tekst mają taką samą nazwę), interfejs wiersza polecenia automatycznie wybiera jedyny element wywołujący. Jeśli wiele jest wywoływanych, wyświetla listę wszystkich dopasowań z ślimakami.
W przypadku wyników wyszukiwania niepodwoływalnych (np. textblock wewnątrz przycisku) wyszukiwanie automatycznie wyświetla najbliższy nadrzędny element — element nadrzędny, którego można użyć z elementem invoke.
Działa to dla wszystkich selektorów wyszukiwania:
lbl-savechanges-a1b2 "Save changes" (120,40 80x20)
^ invoke via: btn-save-c3d4 "Save"
Selektor powierzchniowy może być używany bezpośrednio:
winapp ui invoke btn-save-c3d4 -a myapp # invoke the parent Button
Commands
stan
Połącz się z aplikacją i pokaż informacje o połączeniu.
winapp ui status -a notepad
winapp ui status -a notepad --json
Sprawdzić
Wyświetl drzewo elementów interfejsu użytkownika. Dane wyjściowe pokazują semantycznie slugi z wcięciem 2-spacji dla hierarchii:
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
Przykładowe dane wyjściowe (wartość domyślna):
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)
Przykładowe dane wyjściowe (--interactive — tylko elementy, lista płaska):
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)
Elementy mogą pokazywać następujące znaczniki stanu:
-
[on]/[off]/[indeterminate]— stan przełącznika/pola wyboru -
[collapsed]/[expanded]— stan rozwijania/zwijania drzew, pól kombi, elementów menu -
[scroll:v]/[scroll:h]/[scroll:vh]— kontener przewijany (pionowy, poziomy lub oba) -
[offscreen]— element nie jest widoczny na ekranie -
[disabled]— element nie jest włączony -
value="..."— bieżąca zawartość tekstowa elementów edytowalnych (gdy różni się od nazwy)
wyszukać
Znajdowanie elementów pasujących do selektora. Dane wyjściowe pokazują semantycznie slugi:
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
Przykładowy wynik:
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
btn-close-d1a2 "Close" (1318,206 48x48)
Slugi wyświetlane w danych wyjściowych (np. btn-minimize-d1a0) mogą być używane bezpośrednio z innymi poleceniami:
winapp ui invoke btn-minimize-d1a0 -a notepad
get-property
Odczytywanie wartości właściwości z elementu. Obejmuje stan specyficzny dla wzorca (ToggleState, Value, IsSelected itp.).
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
winapp ui get-property Document -p FontWeight -a myapp --json # document formatting
W nazwach właściwości jest rozróżniana wielkość liter. Nieznana nazwa kończy się niepowodzeniem w invalid_arguments obszarze --json; pomiń --property listę właściwości, w tym wszystkie sześć atrybutów formatowania tekstu poniżej.
wait-for --property używa tych samych nazw z uwzględnieniem wielkości liter i odrzuca nieznane nazwy przed sondowaniem.
Formatowanie tekstu w całym dokumencie
Formatowanie jest odczytywane w całym dokumencie TextPattern elementu, a nie w bieżącym zaznaczeniu ani daszku. Odczyty nie zmieniają fokusu ani zaznaczenia.
| Property | Jednolita wartość (zwracana jako ciąg) |
|---|---|
FontWeight |
Waga liczbowa, taka jak "400" (normalna) lub "700" (pogrubiona) |
FontName |
Nazwa rodziny czcionek, taka jak "Courier New" |
FontSize |
Rozmiar w punktach, takich jak "15.5" |
ForegroundColor |
Windows dziesiętne COLORREF (0x00BBGGRR), np"3678732". dla RGB(12, 34, 56) |
IsItalic |
"True" lub "False" |
StrikethroughStyle |
Styl dekoracji tekstu numerycznego interfejsu użytkownika, taki jak "0" (brak) lub "1" (pojedynczy) |
Liczby używają niezmiennego formatowania (punkt dziesiętny, niezależnie od ustawień regionalnych). Każdy atrybut może zamiast tego zwracać:
| Wartość | Znaczenie i następny krok |
|---|---|
"Mixed" |
Formatowanie różni się w obrębie dokumentu. Nie traktuj go jako jednolitą wartość; To polecenie nie wykonuje zapytań dotyczących poszczególnych zakresów tekstu. |
"NotSupported" |
Dostawca TextPattern dokumentu nie zgłasza tego atrybutu. Sprawdź obsługę ułatwień dostępu aplikacji. |
"Unavailable" |
Element nie ma elementu TextPattern. Użyj inspect polecenia lub search , aby znaleźć jego element tekst/dokument. |
Podczas wyświetlania listy wszystkich właściwości buforowane właściwości podstawowe pozostają dostępne, jeśli nie można rozpoznać elementu dynamicznego, a źle sformułowana wartość formatowania zostanie pominięta bez odrzucania innych właściwości. Te pominięcie są rejestrowane jako ostrzeżenia. Zażądaj określonej właściwości formatowania, aby uzyskać błąd zamiast pominięcia.
Błędy dostawcy pozostają błędami, a nie "Unavailable". W przypadku stale_elementprogramu ponownie sprawdź aplikację i ponów próbę przy użyciu bieżącego selektora.
Koperta JSON zawiera elementId, typizowane elementi ciąg- wartość properties.
Istniejące właściwości, w tym BoundingRectangle, zachowują swoje formaty.
Na przykład część formatowania elementu properties to:
{
"FontWeight": "700"
}
zrzut ekranu
Przechwyć okno lub element jako PNG.
winapp ui screenshot -a notepad # saves screenshot.png in cwd
winapp ui screenshot -a notepad --output my.png # custom filename
winapp ui screenshot --quiet -a notepad -o my.png # save without informational output
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 -w 131906 --capture-screen # one screen region, with visible overlays in place; foregrounds window
winapp ui screenshot -a myapp --focus # bring window to foreground first, then capture (default WGC path)
Bez selektora elementów domyślne przechwytywanie łączy wiele okien w jedną etykietę, obok siebie złożony plik PNG, a nie oddzielne pliki.
-a według nazwy procesu lub piD zawiera okna aplikacji i ich należące do nich okna. Dopasowanie oparte na -a tytułach wybiera jedno pasujące okno i jego należące do niego okna; -w jawnie wybiera jedno okno plus jego okna, a nie każde okno w procesie. W związku z tym okno dialogowe lub etykietka narzędzia może być wyświetlane jako własny panel, nawet jeśli jawnie wybierzesz główny obiekt HWND. Selektor elementów przycina do tego elementu zamiast komponować okna.
--quiet Pomija informacyjne dane wyjściowe zarówno dla przechwytywania pojedynczego okna, jak i złożonego, w tym zapisanej ścieżki. Ostrzeżenia i diagnostyka niepowodzenia przechwytywania pozostają widoczne. Zamiast tego należy --json używać ścieżki i wymiarów pliku jako danych wyjściowych ze strukturą.
Za pomocą parametru --on sandbox--output nazwij miejsce docelowe hosta. Pomyślne, zwykłe dane wyjściowe i --json raport zawierający ścieżkę hosta po dostarczeniu obrazu.
Domyślna ścieżka przechwytywania używa Windows. Graphics.Capture (WGC) odczytywanie rzeczywistej powierzchni złożonej DWM — zachowanie zaokrąglonych narożników, przezroczystości i pracy nawet wtedy, gdy okno jest okludnione przez inne windows. Jeśli usługa WGC jest niedostępna (starsze Windows kompilacje), interfejs wiersza polecenia wraca do usługi PrintWindow.
Użyj --capture-screen -w <hwnd> polecenia , gdy potrzebne są widoczne wyskakujące okienka lub etykietki narzędzi w ich położeniach na ekranie, w tym nakładki, które nie są własnością okna docelowego. Odczytuje ten region ekranu okna, a nie komponowanie oznaczonych panelami i najpierw przenosi okno na pierwszy plan. W programie -awymaga dokładnie jednego pasującego okna. Jeśli dopasowuje się kilka okien najwyższego poziomu lub posiadanych okien, użyj polecenia i ponów próbę za pomocą winapp ui list-windows -a <app> polecenia -w <hwnd>. Użyj --focus polecenia , jeśli chcesz po prostu na pierwszym planie okna bez przełączania trybów przechwytywania (np. aby upewnić się, że zrzut ekranu jest zgodny z tym, co aktualnie patrzy użytkownik).
Ponieważ kontroler domeny ekranu przechwytuje wszystkie elementy rzeczywiście z przodu,
--capture-screensprawdza, czy cel dotarł na pierwszy plan bezpośrednio przed przechwyceniem i niepowodzeniem,foreground_not_targetjeśli nie (zapobieganie kradzieży fokusu, monit kontroli dostępu użytkownika lub innego okna aktywowania się). W tym przypadku nie zapisano żadnego obrazu — wcześniej polecenie zakończyło 0 i przekazało obraz nieprawidłowego okna.ui record --capture-screenstosuje to samo sprawdzenie przed pierwszą ramką.
rekord
Zarejestruj okno lub region elementu w formacie H.264 MP4. Preferuj dodatnie --duration-sec skrypty nienadzorowane. Bez czasu trwania nagrywanie będzie kontynuowane do ctrl+C lub, w przypadku przekierowania stdin, nowego wiersza lub EOF. Npm uiRecord i targetRecord pomocnicy wymagają liczby całkowitej durationSec z zakresu od 1 do 86400; ich sygnał przerwania anuluje siłowo, a nie bezpiecznie sfinalizowanie nagrania.
# Record for 10 seconds
winapp ui record -a myapp --duration-sec 10 --fps 15 --output demo.mp4
# Add agent-readable frames
winapp ui record -a myapp --frames --duration-sec 10 --fps 10 --output evidence.mp4 --json
# Stop an unbounded recording through stdin
"" | winapp ui record -a myapp --json --output capture.mp4
# Include screen overlays and popups
winapp ui record -a myapp --capture-screen --duration-sec 5 --output with-popups.mp4
Opcje:
-
--duration-sec N— rekord przez N sekund. Domyślne 0 rekordów do momentu zatrzymania. -
--fps N— ramki docelowe na sekundę (domyślnie 15). -
--max-edge N— Skalowanie w dół, tak aby najdłuższa krawędź wynosi najwyżej N pikseli (0 = bez skalowania w dół). -
--capture-screen— Przechwytywanie z kontrolera domeny ekranu (obejmuje nakładki/wyskakujące okienka; pierwszego planu). -
--output <path>— ścieżka mp4 danych wyjściowych. Wartość domyślna torecording-<timestamp>-<guid>.mp4. -
--overwrite— zastąp istniejące dane wyjściowe nagrywania po zakończeniu nowego wykonania. Bez niego istniejące dane wyjściowe są odrzucane. -
--frames— Zapisz znacznik czasu ze znacznikami czasu dowody JPEG na<output-name>.frames. Obsługuje 1-30 klatek na sekundę i--max-edge64-4096 (domyślnie 1280). Dane ramek są ograniczone do 1 GiB; MP4 będzie kontynuowane, jeśli osiągnięto limit.
Artefakty ramek z możliwością odczytu agenta:
evidence.mp4
evidence.frames/
manifest.json
frames.ndjson
frames/
frame-000000-t000000000012.jpg
frames.ndjson ma jeden wiersz na próbkę z sampleIndexmonotonicznymi elapsedMs, MP4-względnymi mediaTimeMs, imageIndex, , filei changed. Kolejne próbki identyczne z pikselami ponownie wykorzystają poprzednią jakość-85 JPEG.
manifest.json rejestruje żądanie, chronometraż, stan MP4, wymiary obrazu i stan (complete, partial, lub truncated). Obcięty czas obejmuje zachowany prefiks, podczas gdy video opisuje kompletny plik MP4.
Wybierz nową ścieżkę wyjściową, chyba że zamierzasz zamienić nagranie na --overwrite.
Bez niego istniejące wideo lub jego sparowane .frames katalogi blokują nagrywanie, nawet jeśli pominięto --frames.
Poprzedni plik MP4 pozostaje nienaruszony, jeśli nowe przechwytywanie nie powiedzie się. Po pomyślnym zastąpieniu poprzedni katalog ramki jest zachowywany jako <output-name>.frames.previous-<id>, nawet jeśli nowe nagranie pomija --frames. Zachowaj częściowe dowody i postępuj zgodnie z zgłoszonymi przed recoveryHint ponowną próbą. Jeśli finalizacja mp4 zakończy się niepowodzeniem, zachowane ramki można opublikować w obszarze <output-name>.frames.partial-*. Artefakty ramki zawierają niezaszyfrowaną zawartość ekranu; obsługa ich, takich jak zrzuty ekranu lub wideo.
W programie --on sandboxzarówno plik MP4, jak i katalog ramek są dostarczane do hosta, w tym domyślne dane wyjściowe, gdy --output zostanie pominięty. Zobacz Przechwytywanie piaskownicy , aby zapoznać się z przerwanymi nagraniami i przechwytywaniem całego pulpitu.
Tryby przechwytywania (zgłoszone w polu JSON mode ):
-
wgc— Windows przechwytywania grafiki (ustawienie domyślne; działa, gdy okno jest okludne). -
printwindow— GDI PrintWindow (powrót, gdy usługa WGC jest niedostępna w tym systemie/sesji; uruchom ponownie polecenie ,--capture-screenaby zamiast tego użyć kontrolera domeny ekranu). -
screen— Kontroler domeny ekranu za pośrednictwem (--capture-screenobejmuje nakładki/wyskakujące okienka; przenosi okno na pierwszy plan).
Dane wyjściowe JSON (--json):
-
stdout: Końcowy wynik rejestrowania, w tym cykl, przyczyna zatrzymania, opcjonalne
frameArtifactsi ostrzeżenia. -
stderr: Jeden obiekt JSON na wiersz:
recording-startedzdarzenie po pierwszej klatce, a następnie błąd w przypadku późniejszego zarejestrowania zakończy się niepowodzeniem. Ścieżki ramek są uwzględniane tylko wtedy, gdy dane wyjściowe ramki są aktywne.
Kody błędów:
-
element_not_found— Selektor nie był zgodny. -
ambiguous_selector— Selektor pasował do wielu elementów; użyj sugerowanego ślimaka. -
invalid_arguments— wartość opcji jest nieprawidłowa. -
output_exists— Dane wyjściowe nagrywania już istnieją i nie można ich zastąpić w żądanych opcjach. -
frame_output_failed— nie można zachować żadnego artefaktu po niepodaniu danych wyjściowych ramki. -
partial_output— Ukończono tylko jeden artefakt; sprawdźpartialOutputirecoveryHint.
Znane ograniczenie: Zarejestrowanie elementu w oknie podręcznym może przechwycić okno bazowe. Zarejestruj całe okno lub postępuj zgodnie z przepływem pracy nakładki ekranu dla obrazu. Zobacz #646.
wywołać
winapp ui invoke SettingsCategory -a myapp --action select
winapp ui invoke AgreeCheckbox -a myapp --action toggle-on --json
winapp ui invoke SizeComboBox -a myapp --action expand
winapp ui invoke SubmitButton -a myapp
Użyj --action polecenia , gdy test musi wykonać określoną operację dla dokładnie wybranego elementu. Nigdy nie próbuje innego wzorca lub wywoływanego przodka, nawet jeśli żądana akcja zakończy się niepowodzeniem. Zostanie wybrana kontrolka obsługująca zarówno wywołanie, jak i zaznaczenie, a nie wywoływane z elementem --action select. W przypadku --actionelementu slug jest przeznaczony dokładnie jeden element; selektor zwykłego tekstu lub AutomationId, który pasuje do więcej niż jednego elementu, kończy się niepowodzeniem z kodem zakończenia bezzerowym, a nie działaniem przy pierwszym dopasowaniu, więc przekaż ślimak z inspect/search , gdy nazwa jest niejednoznaczna.
| Action | Operation |
|---|---|
invoke |
InvokePattern.Invoke |
select |
SelectionItemPattern.Select |
toggle |
Przełącznik TogglePattern.Toggle, dokładnie raz |
toggle-on / toggle-off |
Odczyt toggleState; powodzenie bez zmiany już poprawnego stanu, w przeciwnym razie przełącz i zweryfikuj |
expand / collapse |
ExpandCollapsePattern.Expand/Zwiń |
W przypadku toggle-on i toggle-offstan początkowy Indeterminate zezwala na co najwyżej dwa przejścia, sprawdzając stan po każdym z nich. Inne stany początkowe umożliwiają jedno przejście. Jeśli żądany stan nie zostanie osiągnięty, polecenie zakończy się niepowodzeniem, a nie przejściem do przełącznika. Weryfikacja, która zakończyła się niepowodzeniem, może pozostawić kontrolkę zmienioną; przeczytaj ToggleState przed podjęciem decyzji, co należy zrobić dalej.
Bez --actionelementu istniejące zachowanie automatyczne jest niezmienione: try InvokePattern, TogglePattern, SelectionItemPattern, a następnie ExpandCollapsePattern (rozwiń), z wywołalnym-ancestor ponowić próbę w razie potrzeby.
Nieobsługiwana akcja kończy się niepowodzeniem z kodem zakończenia niezerowym i z błędem --jsonustrukturyzowanym w stderr. Sprawdź wybraną kontrolkę i wybierz obsługiwaną przez nią akcję lub jawnie określ docelowy zamierzony element nadrzędny. Kod JSON powodzenia zawiera requestedAction pliki i performedAction; zobacz dokumentację JSON.
click
Kliknij element na jego ekranie współrzędnych przy użyciu symulacji myszy. Służy do kontrolek, które nie obsługują InvokePattern (np. nagłówki kolumn, elementy listy).
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
Podobnie jak inne czasowniki wstrzykiwania danych wejściowych,
clickprzenosi element docelowy na pierwszy plan i szybko kończy się niepowodzeniem (no_interactive_desktopna zablokowanym/bezpiecznym pulpicie,foreground_not_targetjeśli nie można przenieść fokusu) zamiast klikać nieprawidłowe okno. Usuwa również ponownie element tuż przed przyciskiem w dół: po ustawieniu kursora wykonuje jedno końcowe sprawdzanie położenia, więc stale przesuwający się/animujący element docelowy kończy się niepowodzeniemtarget_movedzamiast raportowania powodzenia po kliknięciu wylądował na pustym miejscu — zgłoszony sukces oznacza, że cel był nadal w miejscu, gdy przycisk spadł.
przeciągnąć
Naciśnij przycisk myszy w jednym momencie, przejdź do innego, a następnie zwolnij element za drag <from> <to>pomocą polecenia , gdzie każdy punkt końcowy jest selektorem elementów (przeciąga się z/do środka elementu) lub współrzędnych x,yekranu dokładnie zgodnie z raportem.winapp ui inspect Mieszaj i dopasuj swobodnie (selektor→selector, selektor→koordy, współordy→koordy).
Używa z SendInput ruchami pośrednimi, aby aplikacja widziała realistyczny strumień komunikatów WM_MOUSEMOVE . Służy do zmieniania kolejności/zmieniania rozmiaru uchwytów, suwaków, rysunku kanwy i przeciągania i upuszczania.
winapp ui drag itm-card-9f8e itm-slot-2c1a -a myapp # reorder: card center → slot center
winapp ui drag itm-card-9f8e 300,400 -a myapp # element center → screen coords (from inspect)
winapp ui drag 120,200 480,200 -a myapp # raw screen coords → screen coords
winapp ui drag itm-card-9f8e itm-trash-0001 -a myapp --right # right-button drag
# Press-and-hold / long-press and drop-target dwell
winapp ui drag tile-photo-7b3c tile-photo-7b3c -a myapp --hold-ms 600 # long-press: from == to, hold 600ms, no move
winapp ui drag itm-card-9f8e pane-left-2c1a -a myapp --dwell-ms 350 # settle on the drop target before releasing
Opcje:
-
--right— przeciągnij prawym przyciskiem myszy zamiast lewego przycisku. -
--hold-ms <ms>— Przytrzymaj przycisk w dół na początku przed przeniesieniem (ustawienie domyślne: 0). Z<from> == <to>(bez ruchu) wykonuje gest naciśnięcia i trzymania / długiego naciśnięcia . -
--dwell-ms <ms>— Mieszkaj w miejscu docelowym po przeniesieniu, przed zwolnieniem (wartość domyślna: 0). Pozwala upuścić elementy docelowe /scalać nakładki , które uzbrajają się z trwałego aktywowania (a nie natychmiast pojawia się kursor) zatrzasnąć przed przyciskiem.
Słupki
x,ysą współrzędnymi ekranu w tym samym raporcie spacjiwinapp ui inspect/search, a selektor rozpoznaje środek elementu — najpierw sprawdź punkty, aby wybrać punkty.
Podobnie jak
send-keys --via send-input,dragwstrzykuje współrzędne systemu operacyjnego na ekranie po doprowadzeniu celu na pierwszy plan. Jeśli nie można skierować fokusu do miejsca docelowego (np. zapobiegania kradzieży fokusu z procesu w tle), polecenie kończy się niepowodzeniem (foreground_not_target) zamiast przeciągania na niewłaściwe okno — fokus lub kliknij okno jako pierwsze. Na zablokowanym/bezpiecznym pulpicie kończy się niepowodzeniem z programemno_interactive_desktop. Każdy punkt końcowy elementu jest rozpoznawany ponownie bezpośrednio przed przeciągnięciem; Jeśli nadal przenosi się/zmienia rozmiar (animowanie elementu docelowego), polecenie kończy się niepowodzeniemtarget_movedzamiast przeciągania do nieaktywnego punktu.x,y(Nie można ponownie zweryfikować bez punktów końcowych, więc są używane as-is).
Dotyk
Wstrzykiwanie syntetycznych gestów dotykowych przy użyciu interfejsu API wstrzykiwania wskaźnika Windows. Kotwica kontaktu jest selektorem elementów (używa środka elementu) lub jawną współrzędną x,yekranu za pośrednictwem --at (te same raporty spacjiwinapp ui inspect). Użyj go do interakcji naciśnięcia/naciśnięcia i gestów wielodotyku, których symulacja myszy nie może wyrazić.
winapp ui touch btn-ok-1a2b -a myapp # tap at the element center
winapp ui touch -a myapp --at 320,240 # tap at explicit screen coords
winapp ui touch tile-photo-7b3c -a myapp --gesture long-press --hold-ms 600
winapp ui touch -a myapp --at 100,300 --gesture swipe --to-point 400,300
winapp ui touch img-map-9f8e -a myapp --gesture pinch --distance 200 # pinch-to-zoom out (2 fingers)
winapp ui touch img-map-9f8e -a myapp --gesture stretch --distance 200 # stretch-to-zoom in (2 fingers)
Opcje:
-
--gesture <g>—tap(ustawienie domyślne),double-tap,long-press,swipe,pinch.stretch -
--at <x,y>— jawny punkt początkowy (współrzędne ekranu). Domyślnie jest to środek elementu selektora. -
--to-point <x,y>— Punkt końcowy dla elementuswipe. Ma pierwszeństwo przed--direction. -
--direction <right|left|up|down>— Kierunek przesunięcia (ustawienie domyślne:right). W połączeniu z elementem--distancew celu obliczenia punktu końcowego, gdy--to-pointnie zostanie podany. -
--distance <px>— Rozrzuć palcem napinch/stretchodległość w pikselach lub machnięcia. -
--hold-ms <ms>— Przytrzymaj kontakty w dół przed zniesieniem (czas wstrzymania długiego naciśnięcia; wartość domyślna to 500 ms,long-pressjeśli nie jest ustawiona). -
--duration-ms <ms>— Czas ślizgu dla ruchliwych gestów (szybkie przesunięcie/szczypta/rozciągnięcie; wartość domyślna 300). -
--fingers <n>— liczba kontaktów (1–10; wartość domyślna 1).pinch/stretchzawsze używaj 2.
Bezpieczeństwo iniekcji.
touchodmawia wstrzyknięcia, chyba że uchwyt okna docelowego niezerowego zostanie rozwiązany i to okno zawiera pierwszy plan — kończy się niepowodzeniemno_target, gdy nie można rozpoznać okna,foreground_not_targetjeśli nie można przenieść fokusu lubno_interactive_desktopna zablokowanym/bezpiecznym pulpicie. Każda współrzędna (środek elementu, jawne--at/--to-pointi wygenerowane punkty waypoints) jest sprawdzana względem prostokąta okna docelowego. Punkt poza oknem jest przedstawiany jako ostrzeżenie niekrytyczne (warnings[]wpis w--jsonlub wiersz ostrzeżenia w trybie tekstowym) i iniekcja nadal następuje — dopasowanie czasowników myszy (click/drag/hover/scroll), które również wprowadza współrzędne poza oknem.--fingerspowyższe 10 jest odrzucane z góry.Uwaga sprzętowa. Funkcja Touch preferuje nowoczesne urządzenie wskaźnika syntetycznego (
CreateSyntheticPointerDevice(PT_TOUCH)) i wraca do starszegoInitializeTouchInjection/InjectTouchInputinterfejsu API. Jeśli iniekcja jest nieobsługiwana na bieżącym urządzeniu/sesji, polecenie wyświetla rzeczywisty kod błędu Win32 (np. "nieobsługiwany") zamiast zgłaszać fałszywe powodzenie — traktuj wyjście niezerowe jako "nie dostarczono dotyku".Remote Desktop/sesje maszyn wirtualnych. W przypadku Remote Desktop (RDP) lub niektórych sesji maszyny wirtualnej system operacyjny może zaakceptować syntetyczne dotknięcie (wyjście 0) bez dotarcia do aplikacji docelowej. Po wykryciu
touchsesji zdalnej dołącza ostrzeżenie dotyczące niepewności dostarczania —warnings[]wpis w--jsonsystemie lub wiersz ostrzeżenia w trybie tekstowym. / ✅exit 0 oznacza następnie wywołanie iniekcji powiodło się, a nie , że aplikacja otrzymała dane wejściowe; potwierdź efektui screenshot/ui inspect, gdy ma znaczenie.
długopis
Wstrzykiwanie syntetycznych danych wejściowych pióra/rysika — naciśnięcia i pociągnięcia atramentu — przy użyciu interfejsu API Windows syntetycznego wskaźnika (CreateSyntheticPointerDevice(PT_PEN); Windows 10 1809+). Wyceluj w środek elementu, punkt jawny --at lub pełny --path pociągnięcie pisma odk.
winapp ui pen canvas-1a2b -a myapp # pen tap at the element center
winapp ui pen -a myapp --at 320,240 --pressure 0.8 # firm pen tap at explicit coords
winapp ui pen -a myapp --path "100,100 150,120 210,140 260,120" # draw an ink stroke
winapp ui pen -a myapp --path "100,100 260,100" --eraser # erase along a stroke
winapp ui pen -a myapp --at 200,200 --tilt-x 30 --tilt-y -15 # tilted pen contact
Opcje:
-
--at <x,y>— Punkt styku pióra (współrzędne ekranu). Domyślnie jest to środek elementu selektora. Ignorowane, gdy--pathzostanie podane. -
--path "<x,y x,y …>"— Ścieżka pociągnięć pisma od pisma od ręcznego jako pary rozdzielanex,yodstępami (ścieżka jednopunktowa jest naciśnięciem). -
--pressure <0.0–1.0>— Ciśnienie pióra (domyślnie 0,5). -
--tilt-x <deg>/--tilt-y <deg>— Kąty pochylenia pióra, od −90 do 90 (domyślnie 0). -
--eraser— Użyj końca gumki pióra zamiast końcówki. -
--duration-ms <ms>— Łączny czas podróży pociągnięcia w milisekundach dystrybuowany jako interpolowane ramki AKTUALIZACJI w ścieżce (wartość domyślna: ~10 ms w punkcie drogi). Użyj tej funkcji, aby kontrolować, jak szybko pióro wyraźnie porusza się od początku do końca.
Bezpieczeństwo iniekcji. Podobnie jak
touch,penodmawia wstrzykiwania bez zera, pierwszego planu okna docelowego (no_target/foreground_not_target/no_interactive_desktop) i sprawdza każdy punkt pisma odręcznego względem prostokąta okna docelowego, wyświetlając dowolną współrzędną poza oknem jako ostrzeżenie niekrytyczne (warnings[]w--json, lub w wierszu ostrzeżenia w trybie tekstowym) podczas wstrzykiwania — zgodne z czasownikami myszy. Nieprawidłowe--pressure(poza 0.0–1.0) lub przechylenie (na zewnątrz ±90°) są odrzucane z góry.Remote Desktop/sesje maszyn wirtualnych. Routing piórem jest szczególnie zawodny w przypadku Remote Desktop: wywołanie iniekcji może zgłosić powodzenie (wyjście 0), podczas gdy żadne dane wejściowe pióra nie docierają do aplikacji. Po wykryciu
pensesji zdalnej dołącza ostrzeżenie o niepewności dostarczania (warnings[]w systemie lub wierszu ostrzeżenia w--jsontrybie tekstowym), dzięki czemu ✅ potwierdzenie dostarczenia nie jest mylące. Zweryfikuj przepływy zależne od pióra na lokalnym, interaktywnym pulpicie.
wskaźnik aktywowania
Przenieś wskaźnik myszy na środek elementu, aby wyzwolić efekty aktywowania (etykietki narzędzi, wysuwane, stany wizualne). Używa SendInput do realistycznego ruchu myszy z małym przełącznikiem, a następnie czeka na konfigurowalny czas zamieszkania.
winapp ui hover btn-info-a1b2 -a myapp # hover with default 800ms dwell
winapp ui hover btn-info-a1b2 -a myapp --dwell-time 1200 # longer dwell for slow tooltips
winapp ui list-windows -a myapp # use the main window's HWND below
winapp ui hover btn-info-a1b2 -a myapp; winapp ui screenshot -w <hwnd> --capture-screen # hover then capture tooltip in place
Opcje:
-
--dwell-time <ms>— czas w milisekundach oczekiwania po umieszczeniu wskaźnika myszy na wyświetlaniu efektów (wartość domyślna: 800, zakres: 0–10000)
send-keys
Wysyłanie syntetycznych danych wejściowych klawiatury — odpowiednik klawiatury do click. UIA nie ma wzorca iniekcji klawiatury, więc to spada do warstwy Win32. Służy do nawigacji za pomocą klawiatury (strzałek, klawiszy Tab, Enter, Esc), skrótów (ctrl+c, alt+f4), i wpisywania w kontrolkach, które wymagają zdarzeń naciśnięć klawiszy, a nie set-valuedo zapisu niepodzielnego.
winapp ui send-keys "down down enter" -a myapp # arrow navigation then commit
winapp ui send-keys "ctrl+a delete" -a myapp # select all, then delete
winapp ui send-keys "Hello world" --target txt-name-a1b2 -a myapp # focus a field, then type text
winapp ui send-keys "text=down text=down text=enter" -a myapp # type the words, don't press the keys
winapp ui send-keys "down down enter" -a myapp --verbatim # same, but type the whole argument literally
winapp ui send-keys "alt+f4" -a myapp # close window via accelerator
winapp ui send-keys "vk=0x5D" -a myapp # a key with no friendly name (Apps/Menu key)
winapp ui send-keys "ctrl+shift+t" -a myapp --via send-input # use OS-wide injection instead of PostMessage
winapp ui send-keys "win+shift+v" -a myapp --via send-input --allow-system-keys # opt in to drive a global hotkey
Gramatyka klucza (tokeny rozdzielane odstępami, cudzysłów ciągi wielo tokenów):
-
Nazwane klucze —
enter/return,tab.esc/escapespacebackspacedelete/delinserthomeendpageup/pguppagedown/pgdnup/down/left/rightf1f16appsprintscreencapslock -
Sekwencje — wiele tokenów jest naciskanych w kolejności:
down down enter. -
Kombi modyfikatora —
ctrl,shiftaltwinsprzężone z+:ctrl+shift+t, .alt+f4 -
Tekst literału — każdy token, który nie jest znanym kluczem, jest wpisany znakiem według znaku:
hello. Sąsiadujące wyrazy literału zachowują odstęp między nimi, więc fraza w cudzysłowie, taka jak"Hello world", jest wpisywana dosłownie (spacja jest zachowywana); literał, który zawiera tylko+tekstC++luba+bjest wpisywany jako tekst, a nie analizowany jako kombi. -
Jawna ucieczka literału — prefiks tokenu z
text=, aby wpisać go dosłownie, nawet jeśli zderzy się z nazwą klucza lub modyfikatora:text=enterwpisz wyraz "enter" zamiast naciskać klawisz Enter itext=ctrl+awpisz ciąg literału. Odzwierciedla ucieczkęvk=; unikniętą wartość nadal łączą się z sąsiednimi wyrazami literału (text=down low→ "w dół niski"). Ponieważ tokeny są rozdzielone odstępami (a sąsiadujące literały ponownie łączą się z jedną spacjątext=), użyj ukośników odwrotnych wewnątrz wartości , aby wpisać odstępy, które nie przetrwałyby w przeciwnym razie:\s→ spacji, → tabulatora,\t→ nowego wiersza,\n→ nowego wiersza,\r\\→ ukośnik literału.\n,\ri\r\nkażda wstawia pojedynczy podział wiersza (Enter /VK_RETURN), tak itext=line1\nline2text=line1\r\nline2wpisz jeden nowy wiersz. Dlategotext=a\s\sbwpisze "a b" (podwójna spacja) itext=\shiutrzymuje wiodącą przestrzeń. Nierozpoznana ucieczka (np.\x) jest pozostawiona dosłownie. -
Literał całego argumentu (
--verbatim) — gdy cały ładunek jest tekstem literału, należy przekazać--verbatimzamiast uciekać przed każdym tokenem za pomocątext=polecenia . Typuje cały argument kluczy dokładnie tak, jak podane — bez nazwanego klucza/kombi/vk=/text=interpretacji — i, w przeciwieństwie do normalnej ścieżki, zachowuje dokładne białe znaki wewnętrzne (bez zwijania\s) bez konieczności . Dlategosend-keys "down down enter" --verbatimwpisze wyrazy isend-keys "a b" --verbatimutrzymuje podwójną spację. Ucieczki ukośnika odwrotnego nie są dekodowane w--verbatimtrybie (\sznak jest wpisywany jako ukośnik odwrotny i znak "s"); należy użyćtext=tokenu, gdy potrzebujesz znaku kontrolki z znakiem ucieczki. -
Nieprzetworzone klucze wirtualne —
vk=0xNN(szesnastkowe) lubvk=NN(dziesiętne) dla kluczy bez przyjaznej nazwy.
Opcje:
-
--target <selector>— Fokusuj ten element (za pośrednictwem interfejsu użytkownika) przed wysłaniem kluczy. Bez niego klucze przechodzą do aktualnie ukierunkowanego elementu aplikacji. -
--verbatim— Wpisz cały argument keys jako tekst literału (bez klucza/kombi/vk=/text=analizowania) i zachowaj dokładne odstępy. Cała forma argumentu ucieczki na tokentext=. -
--via <transport>—post-message(domyślnie) wpisyWM_KEYDOWN/WM_KEYUP/WM_CHARw kolejce okna docelowego. Jest przeznaczony dla HWND i pomija interfejs użytkownika (działa na różnych poziomach integralności).send-inputwstrzykuje system operacyjny przez iSendInputprzechodzi do okna pierwszego planu.
Wybieranie transportu/znanych limitów:
-
post-messagejest wartością domyślną, ponieważ pomija interfejs użytkownika i nie zależy od pierwszego planu okna. Limity: nie może wyzwalać globalnych kluczy dostępu zarejestrowanych za pośrednictwemWH_KEYBOARD_LLpunktów zaczepienia niskiego poziomu (te naciśnięcia wejścia nadrzędnego dowolnego kolejki okien) i aplikacje odczytujące stan klucza pierwotnego za pośrednictwemGetAsyncKeyStatemogą nie obserwować zatrzymanych modyfikatorów. Automatycznie rozpoznaje i publikuje w docelowym oknie podrzędnym wątku (za pośrednictwemGetGUIThreadInfo) po pierwszym planie, dlatego klasyczne aplikacje Win32/WinForms, których kontrolki są oddzielnymi oknami podrzędnymi, odbierają klucze bez ręcznego określania wartości docelowej kontrolki. Aplikacje WinUI 3 / UWP mają bez okien kontrolki XAML bez podrzędnego HWND, więc wysłanaWM_CHAR/WM_KEYDOWNnie ma nic do lądowania i jest porzucona — po komunikacie nie można ich napędzać (polecenie ostrzega i kończy 0); użyj polecenia--via send-input. (WPF okna to single-HWND i klucze trasy do wewnętrznie ukierunkowanego elementu, więc post-message działa tam.) -
send-inputTworzy w pełni rzeczywiste dane wejściowe (modyfikatory widoczne dlaGetAsyncKeyState, uruchamia punkty zaczepienia niskiego poziomu), ale przechodzi do dowolnego okna na pierwszym planie i jest blokowany przez interfejs UIPI podczas wstrzykiwania z procesu z podwyższonym poziomem uprawnień do celu niższej integralności (AppContainer/AppX). Jeślisend-inputzgłasza błąd, element docelowy jest prawdopodobnie podwyższony lub aplikacja AppX — użyj poleceniapost-messagelub uruchom interfejs wiersza polecenia na zgodnym poziomie integralności. Jako ochroniarz sprawdza,send-inputczy okno docelowe znajduje się na pierwszym planie bezpośrednio przed wstrzyknięciem i niepowodzeniem (foreground_not_target), zamiast wpisywać w niewłaściwym oknie , jeśli fokus nie może zostać przeniesiony do niego — fokus lub kliknij okno jako pierwsze. Zamiast tego na zablokowanym lub bezpiecznym pulpicie kończy się niepowodzeniemno_interactive_desktop(nie istnieje okno pierwszego planu do wstrzykiwania) — odblokuj sesję lub użyj czasownika UIA-pattern (set-value,invoke). -
Kombi zarezerwowane systemu (
win+l,win+r,ctrl+shift+escctrl+alt+delalt+tabalt+f4ctrl+escsamotnewin/printscreen, ...) działają na systemie operacyjnym/powłoce, a nie tylko na obiekcie docelowym podczas wysyłania całego systemu operacyjnego.send-inputodrzuca je domyślnie (błędy zinvalid_argumentsi wysyła nic), ponieważ wstrzykiwanie ich na poziomie systemu operacyjnego ma efekty daleko poza okno docelowe (np.win+lzablokowałoby sesję). Przekaż--allow-system-keys, aby wyrazić zgodę — umożliwia to prowadzenie globalnego klucza dostępu, takiego jak PowerToys'win+shift+vlubwin+r(globalny punkt zaczepienia niskiego poziomu obserwuje strumień wejściowy dla całego systemu operacyjnego, więc wstrzyknięte kombi go uruchamia). Wyjątki, które pozostają zablokowane nawet--allow-system-keysza pomocą polecenia :win+lblokuje stację roboczą, za pomocąLockWorkStation()której nie można rozpoznać z automatyzacji (przerywa sesje ciągłej integracji i pulpitu zdalnego) ictrl+alt+deljest sekwencją bezpiecznej uwagi (SAS), która Windows spada z wstrzykniętych danych wejściowych niezależnie od flagi — nigdy nie może obowiązywać, więc błędy (invalid_argumentswyjście 1) zamiast zgłaszać wprowadzający błąd. Inne kombi (alt+f4,ctrl+shift+esc, ,win+r...) stają się dozwolone za pomocą flagi — uważaj, wywołując. Alternatywnie, aby dostarczyć kombi systemowej do określonego okna, użyj--via post-messagepolecenia , który jest zakresem okna i nie ma to wpływu (wysłanawin+ljest nieszkodliwa, choć wysłanaalt+f4nadal zamyka okno docelowe).
Zdarzenia na naciśnięciu klawiszy (KeyDown/ TextChanged):
-
Nazwane klucze i kombi modyfikatora (
down,enter,ctrl+shift+t,vk=0xNN) uruchamiają rzeczywisteKeyDown(iKeyUp) w obu transportach — są dostarczane jako dyskretneWM_KEYDOWN/WM_KEYUP(lubSendInputzdarzenia klucza wirtualnego). -
Tekst wpisany literał (
hello) różni się transportem:-
--via send-inputmapuje każdy znak na jego wirtualny klawisz (plus Shift) na aktywny układ klawiatury, więc element docelowy widzi prawdziwyKeyDownz poprawnym kluczem wirtualnym , a następnie skomponowanyWM_CHARprzez system operacyjny (podnoszącyTextChanged) — tj. jeden pełny naciśnięty klawisz na znak. Znaki nieosiągalne w bieżącym układzie (lub wymagając klawiszy Ctrl/AltGr) wracają do pakietu Unicode, więc dokładny znak nadal ląduje. Użyjsend-inputpolecenia , jeśli potrzebujesz wierności na naciśnięciuKeyDownklawiszy (np. jazdy winUI 3 / WPFTextBox, którego klucz obsługi jest wyłączonyKeyDown). W przypadku normalnego (innego niż podwyższony poziom) hosta testowego WinUI 3 przełącz okno na pierwszy plan (winapp ui focus/ kliknięcie go), ponieważsend-inputjest ono przeznaczone dla okna pierwszego planu. -
--via post-messagepublikuje pojedynczy znakWM_CHAR( nie publikujeWM_KEYDOWN/WM_KEYUPtekstu wpisanego — są one zarezerwowane dla nazwanych kluczy/kombi), które nie zgłaszają znaku na znakKeyDown. Automatycznie retargets do ukierunkowanej kontrolki podrzędnej okna, więc klasyczne kontrolki Edycji Win32/WinFormsWM_CHARwylądować tekst (podnoszenieTextChanged). Zastrzeżeniem: Aplikacje WinUI 3/ UWP/ XAML (podstawowy element docelowy aplikacji WinApp) mają kontrolki bez okien ignorowaneWM_CHAR/WM_KEYDOWN— więc ani tekst literału , ani nazwane klucze (Enter, cyfry, ...) nie docierają do nich, mimo że polecenie zgłasza sukces. Emituje ostrzeżenie, gdy element docelowy wygląda jak XAML i nadal kończy wartość 0 (PostMessagejest uruchamiany i pomijany i nie może potwierdzić dostarczenia). Służy--via send-inputdo kierowania aplikacjami WinUI 3/UWP/WPF; zarezerwujpost-messagedla klasycznych kontrolek Win32 lub gdy są potrzebne tylko okna obejmujące poziomy integralności.
-
Dane wyjściowe JSON (--json): wynik hwnd jest skutecznym oknem, do którego zostały dostarczone klucze — w tym --via post-message celu jest to rozpoznana skoncentrowana kontrolka podrzędna, gdy polecenie retargetsuje do niego (niekoniecznie okno najwyższego poziomu-w/-a/-e), dzięki czemu automatyzacja może potwierdzić dokładnie, gdzie wylądowały dane wejściowe. Gdy ten skuteczny element docelowy wygląda jak host XAML bez okien, powyższe zastrzeżenie dostarczania jest również widoczne jako warnings[] wpis (ten sam poradnik pokazany w konsoli), więc ✅ wyjście 0 nie jest mylące z potwierdzonym dostarczeniem.
set-value
Ustaw wartość elementu edytowalnego programowo (bez naciśnięć klawiszy, bez pierwszego planu aplikacji). Używa łańcucha rezerwowego:
- ValuePattern — kontrolki TextBox, ComboBox, PasswordBox i najbardziej edytowalne.
- RangeValuePattern — kontrolki liczbowe (Slider, ProgressBar), gdy wartość jest analizowana jako liczba.
-
LegacyIAccessible (
IAccessible::put_accValue) — powrót do kontrolek edycji tylko textPattern , które nie uwidaczniają elementu ValuePattern (np. pola edycji sformatowanej/Documentredagowania). Spowoduje to zamknięcie luki odczytu/zapisu, w którejget-valuemożna odczytać taką kontrolkę, aleset-valuenie może.
winapp ui set-value txt-textbox-a4b1 "Hello world" -a notepad
winapp ui set-value sld-volume-b2c3 75 -a myapp
winapp ui set-value doc-compose-9f3a "hello" -a myapp # RichEdit/compose box via LegacyIAccessible
Jeśli żadna z trzech wzorców nie może ustawić wartości, set-value kończy się niepowodzeniem z wyraźnym błędem wskazującym w send-keys ostateczności.
Nie każdy bogaty edytor obsługuje zestaw programowy. Rezerwowy element LegacyIAccessible działa tylko na kontrolkach, których implementacje ułatwień dostępu — natywne kontrolki
IAccessible::put_accValueedycji Win32 i Chromium/Electron/WebView2 zwykle wykonują powierzchnie. WinUI 3RichEditBoxi WPFRichTextBoxnie obsługują programowego ustawiania wartości — zgodnie z projektem uwidaczniają zawartość automatyzacja interfejsu użytkownika jako tylko do odczytu (wzorzec tekstowy, bez wzorca wartości ustawionej), więcset-valuenie można do nich zapisywać. Użyjsend-keys(który wymaga odblokowanego, pierwszego planu pulpitu) dla tych.
get-value
Odczytaj bieżącą wartość z elementu. Używa inteligentnego łańcucha rezerwowego: TextPattern (RichEditBox, Document) → ValuePattern (TextBox, Slider) → SelectionPattern (ComboBox, RadioButton, TabView) → Name (etykiety).
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 get-value SearchBox -a myapp --json
winapp ui wait-for SearchBox -a myapp --value "" --timeout 5000
Pomyślnie odczytane puste pole tekstowe zwraca "text": ""wartość , a nie etykietę ułatwień dostępu. Zawartość tylko do odstępów jest zachowywana w formacie JSON.
wait-for --value "" pasuje do pustego pola, niezależnie od tego, czy jest ono świeże, czy też zostało wyczyszczone po edycji. Aby zamiast tego odczytać etykietę ułatwień dostępu, użyj polecenia get-property --property Name.
focus
winapp ui focus txt-textbox-a4b1 -a notepad
Aktywuje okno wybranej kontrolki w razie potrzeby, a następnie koncentruje się na kontrolce.
Selektor jest wymagany; użyj -a <app> polecenia lub -w <HWND> wybierz element docelowy.
Powodzenie oznacza, że okno było pierwszym planem, a wybrana kontrolka została HasKeyboardFocus potwierdzona przed zwróceniem polecenia. Polecenie umożliwia do 500 ms, aby kontrolka zgłaszała fokus; zatrzymuje się, jeśli cel zniknie lub straci pierwszy plan, a nie próbuje skupić się z powrotem. Okno dialogowe należące przed głównym oknem nie wystarczy: wybierz kontrolkę w oknie dialogowym, jeśli jest to element docelowy.
To polecenie wymaga odblokowanego, interaktywnego pulpitu i nie pomija Windows ograniczeń aktywacji. Jeśli zakończy się to niepowodzeniem z poleceniem foreground_not_target, ręcznie aktywuj zamierzone okno i sprawdź okno dialogowe blokowania przed ponowieniem próby.
W przypadku focus_not_acquiredprogramu sprawdź bieżący interfejs użytkownika i wybierz kontrolkę z możliwością koncentracji uwagi.
W przypadku stale_elementpolecenia odnajduj ponownie obiekt docelowy za pomocą inspect polecenia lub search.
Zachowaj ten sam --on element docelowy w poleceniach odnajdywania i ponów próbę.
przewiń do widoku
Przewiń element do widocznego obszaru.
winapp ui scroll-into-view itm-targetitem-c3d4 -a myapp
wait-for
Poczekaj, aż element pojawi się, zniknie lub osiągnie wartość docelową.
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
Przewiń
Przewiń element kontenera. Znajdź kontenery search scroll z możliwością przewijania — poszukaj [scroll:v] (pionowych) lub [scroll:h] (poziomych) znaczników.
# 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
# Synthesize real mouse-wheel input over the element (1 = one notch up, -1 = one notch down).
# Use this to test handlers that respond to the wheel directly (zoom, custom scroll) rather than ScrollPattern.
winapp ui scroll img-map-a1b2 --wheel -1 -a myapp
Opcje:
-
--direction <up|down|left|right>— przewijanie przyrostowo za pomocą metodyScrollPattern. -
--to <top|bottom>— Przejdź do początku/końca za pomocą poleceniaScrollPattern. -
--wheel <notches>— Syntetyzowanie danych wejściowych kół myszy na środku elementu za pośrednictwemSendInput, w wycięciach kół (wcięciach):1= jeden nacięcie w górę/odchyła,-1= jeden nacięcie w dół/w kierunku,3= trzy cale w górę. (Każdy wycięcie jest WindowsWHEEL_DELTAz 120 jednostek, któreSendInputzużywają; interfejs wiersza polecenia skaluje nacięcie o 120 dla Ciebie).ScrollPatternPomija .
--direction,--toi--wheelwzajemnie się wykluczają — dokładnie jeden. Ze względu--wheelna to, że wstrzykuje dane wejściowe dla całego systemu operacyjnego na współrzędnych ekranu, element docelowy jest pierwszy na pierwszym planie i kończy się niepowodzeniem (foreground_not_target), jeśli nie można przenieść fokusu, a nie przewijania niewłaściwego okna.
uzyskiwanie fokusu
winapp ui get-focused -a myapp
winapp ui get-focused -w <HWND> --json
Pokaż element, który aktualnie ma fokus klawiatury w wybranej aplikacji, w tym kontrolki, których własność aplikacji jest dostępna tylko za pośrednictwem okna nadrzędnego.
W programie -wfokus musi należeć do tego okna, a nie do innego okna lub należącego do niego wyskakującego okienka w tym samym procesie. W programie -asą uwzględniane inne okna w wybranym procesie.
Dane wyjściowe JSON nie hasFocus:false mogą być weryfikowane jako należące do elementu docelowego. Jeśli zapytanie fokusu lub właściciel okna zakończy się niepowodzeniem, polecenie kończy działanie bezzerowe; ponów próbę get-focusedi ponownie odnajduj okno, list-windows jeśli zostało zamknięte.
list-windows
Wyświetl listę wszystkich widocznych okien dla aplikacji, w tym wyskakujących okienek i okien dialogowych. Domyślnie okna bez tytułu o zerowym rozmiarze (niewidoczne okna systemowe) są wykluczone.
winapp ui list-windows -a imageresizer
winapp ui list-windows -a Terminal
winapp ui list-windows # all windows (no filter)
winapp ui list-windows --show-hidden # include invisible zero-size windows
tymczasowo wstrzymać
Zwolnij interfejs użytkownika tego przepływu pracy na wczesnym etapie zamiast czekać na czterosekundową łaskę bezczynności. Wymaga WINAPP_UI_WORKFLOW_ID; nie przyjmuje aplikacji i nie selektora. Zobacz Zwalnianie kolei wcześnie.
winapp ui yield
winapp ui yield --json # {"released": true} — or false when nothing was held
Obsługa struktury
| Framework | Sprawdzić | wyszukać | wywołać | set-value | zrzut ekranu |
|---|---|---|---|---|---|
| WPF | ✅ Pełne drzewo | ✅ Wszystkie właściwości | ✅ Wszystkie wzorce | ✅ ¹ | ✅ |
| Windows Forms | ✅ | ✅ | ✅ | ✅ | ✅ |
| Win32 | ✅ | ✅ | ✅ | ✅ | ✅ |
| WinUI 3 | ✅ | ✅ | ✅ | ✅ ¹ | ✅ |
| Elektron | ⚠✔ Drzewo chromowe | ⚠✔ Ograniczone | ⚠✔ Różni się | ⚠✔ Różni się | ✅ |
| Flutter | ⚠✔ Podstawowa | ⚠✔ Podstawowa | ❌ Minimalne | ❌ | ✅ |
¹ set-value działa na dowolnej kontrolce, która uwidacznia wartośćPattern/RangeValuePattern, oraz kontrolki edycji tylko textPattern, których ułatwienia dostępu implementują IAccessible::put_accValue (legacyIAccessible rezerwowy).
WinUI 3 RichEditBox i WPF RichTextBox są wyjątkami — uwidaczniają tylko wzorzec tekstowy tylko do odczytu (bez wzorca wartości możliwej do ustawienia), aby nie można było ustawić ich programowo; należy użyć send-keys (wymagany pulpit interaktywny) do wpisywania w nich.
Korzystanie z aparatu z własnego kodu
Wszystko winapp ui , co robi, jest dostępne jako biblioteka, więc możesz prowadzić tę samą automatyzację z poziomu testu lub narzędzia bez powłoki do interfejsu wiersza polecenia:
| Pakiet | Co dodaje |
|---|---|
Microsoft.Windows.SDK.BuildTools.WinApp.UIAutomation |
Inspekcja, selektory, interakcja wzorca UIA, iniekcja danych wejściowych, zrzuty ekranu |
Microsoft.Windows.SDK.BuildTools.WinApp.UIAutomation.Recording |
Nagrywanie wideo do mp4, plus pakiety ramek |
var services = new ServiceCollection().AddLogging().AddWinAppUiAutomation().BuildServiceProvider();
var ui = services.GetRequiredService<IUiAutomation>();
var target = UiTarget.FromWindowHandle(myWindowHandle);
var save = await ui.FindSingleElementAsync(target, new UiSelector { Query = "Save" }, default);
await ui.InvokeAsync(target, save!, default);
W przypadku akcji deterministycznych użyj przeciążenia wykonującego UiInvokeActionpolecenie :
var selected = await ui.FindSingleElementAsync(
target, new UiSelector { Query = "Save" }, requireUnique: true, default);
if (selected is null) throw new InvalidOperationException("Save was not found.");
UiInvokeActionResult result = await ui.InvokeAsync(target, selected, UiInvokeAction.Invoke, default);
requireUnique: true odrzuca niejednoznaczny tekst zamiast wybierać dopasowanie wywoływane. Dokładne dopasowania AutomationId mają pierwszeństwo przed nazwami lub podciągami AutomationId; Unikatowa nazwa nadal może wybrać kontrolkę, której identyfikator AutomationId jest udostępniany.
W przypadku elementu docelowego o zakresie aplikacji ta kontrola obejmuje wszystkie jej aplikacje/należące do niego okna. Użyj -w <HWND> (lub obiektu docelowego biblioteki jawnej okna), aby ograniczyć zakres zaznaczenia.
Zwraca Pattern wartość i PerformedAction z tymi samymi znaczeniami co wynik akcji interfejsu wiersza polecenia.
Przekaż element zwrócony przez inspekcję lub wybór, używając jego parametru slug środowiska uruchomieniowego lub unikatowego identyfikatora AutomationId bez zmian. Jawne akcje odrzucają brakującą lub niejednoznaczną tożsamość, a nie powiązanie ich według nazwy i typu kontrolki.
W przypadku odczytów o określonym zakresie ustaw UiSelector.Root wartość na inną UiSelectorControlType , na nazwę typu i ClassName na klasę dostawcy literału. Używają one tych samych predykatów zapytań co interfejs wiersza polecenia. Obsługiwany jest tylko jeden poziom główny: selector.Root.Root musi mieć wartość null. Zagnieżdżony katalog główny jest zgłaszany ArgumentException przed wyszukaniem okna docelowego. Użyj unikatowego głównego identyfikatora AutomationId lub ślimaka zamiast zagnieżdżania selektorów głównych.
UiControlTypes.GetId(name) rozpoznaje oficjalne nazwy typów i dwa udokumentowane aliasy, zwracając 0 dla nieprawidłowej nazwy.
UiControlTypes.GetName(id) zwraca nazwę kanoniczną lub Unknown(id) nierozpoznany identyfikator.
Podczas przekazywania przywróconego UiElement z formatu JSON do GetTextAsync lub GetPropertiesAsynczachowaj jego Selector wartości i WindowHandle. Selektor ślimaka musi nadal identyfikować oryginalny element; jeśli już nie istnieje, te operacje odczytu zgłaszają UiElementNotFoundException zamiast wybierać inny element o tej samej nazwie lub identyfikatorze AutomationId. Uruchom ponownie oryginalne zapytanie, aby odświeżyć wynik. Odczyty w zakresie propagują również błędy z ogólnych modułów pobierających właściwości UIA i uzyskanych wzorców UIA zamiast zwracać wartość null lub wcześniej przechwyconą wartość.
Nagrywanie jest oddzielnym pakietem, dzięki czemu projekty, które sprawdzają tylko interfejs użytkownika dysku i nie ściągają interfejsu użytkownika SkiaSharp. Pakiet automatyzacji jest przeznaczony zarówno dla elementów docelowych, jak net10.0-windows i net10.0-windows10.0.19041.0; ten ostatni dodaje Windows graphics capture, co umożliwia screenshot przechwytywanie okludów lub złożonych procesorów GPU windows. Zobacz plik README każdego pakietu w pakiecie NuGet, aby uzyskać pełny interfejs API i kompromis w strukturze docelowej.
UiTarget.FromWindowHandle to punkt wejścia dla platform testowych, które już przekazują okno — na przykład MSTest.Windows.UIAutomation, którego WindowTest.MainWindow element to UIA2 AutomationElement , który jest mostkiem przez program za pomocą MainWindow.Current.NativeWindowHandlepolecenia .
Troubleshooting
| Error | Przyczyna | Rozwiązanie |
|---|---|---|
| "Nie znaleziono uruchomionej aplikacji" | Aplikacja nie działa lub niezgodność nazw | Sprawdzanie nazwy procesu lub używanie identyfikatora PID |
| "Dopasowanie wielu okien" | Niejednoznaczna -a wartość |
Użyj -w <HWND> z wymienionych opcji |
| "ma wiele okien" | Proces ma wiele okien | Użyj -w <HWND> polecenia , aby kierować do określonego elementu |
| "Selektor pasował do N elementów" | Niejednoznaczny selektor starszej wersji | Użyj slugs z inspect danych wyjściowych lub dołącz [0]element , [1] do starszych selektorów |
| "Element mógł ulec zmianie" | Skrót Slug nie pasuje do bieżącego elementu |
inspect Uruchom ponownie lubsearch, aby uzyskać świeże ślimaki |
| "nie obsługuje żadnego wzorca wywołania" | Nie można wywołać elementu | Użyj inspect elementu w celu znalezienia elementu podrzędnego z możliwością wywołania |
| "Nie znaleziono okna UIA" | Interfejs użytkownika nie widzi procesu | Użyj list-windows polecenia , aby znaleźć HWND, a następnie -w |
| "Okno ma zerowy rozmiar" | Okno jest zminimalizowane | Aplikacja zostanie przywrócona automatycznie |
| Wyskakujące okienko/lista rozwijana nie są na zrzucie ekranu | Przechwytywanie domyślne to okno i nie obejmuje nieumyślnych nakładek | Postępuj zgodnie z przepływem pracy nakładki ekranu , aby wybrać okno z -w <hwnd> --capture-screen |
foreground_not_target z --capture-screen |
Windows odmówił aktywacji, więc przechwytywanie ekranu zarejestrowałoby okno, które rzeczywiście znajduje się przed | Kliknij okno docelowe lub zamknij okno kradzieży fokusu i ponów próbę lub upuść --capture-screen |
element_not_found podczas rejestrowania |
Selektor podany, ale bez pasującego elementu |
inspect Uruchom ponownie lubsearch, aby uzyskać nowy selektor |
| Usługa WGC jest niedostępna podczas rejestrowania | Nie można przechwycić init przechwytywania WGC; brak cichego powrotu | Sprawdź procesor GPU/sterownik; użyj --capture-screen polecenia , aby wyrazić zgodę na przechwytywanie ekranu kontrolera domeny |
Typowe wzorce
Nawigowanie i weryfikowanie
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
Znajdowanie tekstu i wywoływanie jego elementu nadrzędnego
# 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
Uściślanie zduplikowanych elementów
winapp ui search '#Image' -a myapp; winapp ui invoke itm-image-a2b3 -a myapp
Zrzut ekranu przedstawiający wyskakujące nakładki
winapp ui list-windows -a myapp # use the main window's HWND below
winapp ui set-value txt-searchbox-e5f6 "query" -a myapp; winapp ui screenshot -w <hwnd> --capture-screen
Nawigowanie, oczekiwanie i weryfikowanie (pojedynczy łańcuch)
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
Odnajdywanie, klikanie i weryfikowanie
winapp ui inspect -a myapp --interactive; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp
Interakcja okna dialogowego pliku
Okna dialogowe otwierania/zapisywania plików to standardowe okna dialogowe Windows z obsługą interfejsu użytkownika:
# 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>
Użyj inspect -w <dialog-hwnd> --interactive polecenia , aby odnaleźć rzeczywiste ślimaki dla określonego okna dialogowego.
Dlaczego ; łańcuch (nie &&)
Operator programu PowerShell może blokować się, gdy natywny && interfejs wiersza polecenia zapisuje w programie stderr lub używa sekwencji ucieczki ANSI. Zamiast tego — ; uruchamia każde polecenie bezwarunkowo i unika tego zakleszczenia. Jest to również lepsze w przypadku przepływów pracy agenta: zwykle chcesz, aby zrzut ekranu był uruchamiany nawet wtedy, gdy wywołanie miało wyjście niezerowe.
Wzorce testowania ciągłej integracji
Użyj winapp ui poleceń w potokach ciągłej integracji (GitHub Actions, Azure DevOps) na potrzeby testów weryfikacyjnych kompilacji i weryfikacji interfejsu użytkownika.
wait-for z --property i --value działa jako asercja — zwraca kod zakończenia 1 w przypadku przekroczenia limitu czasu, co powoduje automatyczne niepowodzenie kroku ciągłej integracji.
Uruchamianie i testowanie w 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
Stan elementu assert z wait-for
wait-for --value sonduje, dopóki wartość elementu nie pasuje do oczekiwanego ciągu, używając tego samego inteligentnego rezerwowego co get-value (TextPattern → ValuePattern → SelectionPattern → Name). Zwraca kod zakończenia 0 zgodny, kod zakończenia 1 w przypadku przekroczenia limitu czasu — co czyni go asercją przyjazną dla ciągłej integracji. Zamiast tego użyj polecenia --property , aby sprawdzić określoną właściwość UIA.
# 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
Potwierdzanie przy użyciu danych wyjściowych JSON
Używanie z --json programem PowerShell lub jq w celu uzyskania bardziej złożonych asercji:
Kontrakt zakończenia kodu dla
searchiwait-forw--jsontrybie: gdy żaden element nie pasuje (search) lub limit czasu oczekiwania (wait-for), polecenie zapisuje w pełni analizowalne koperty wyniku do stdout ({ "matchCount": 0, ... }lub{ "found": false, "timedOut": true, ... }) i zwraca kod zakończenia 1. Stderr jest pusty w--jsontrybie (dane wyjściowe rejestratora są pomijane). Rozgałęzij na polach koperty lub na$LASTEXITCODE, w zależności od tego, co jest bardziej ergonomiczne.
# 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)" }
# Read typed element state while preserving the legacy string property map
$property = winapp ui get-property "Counter Display" -a $pid --json | ConvertFrom-Json
if ($property.element.type -ne "Text") { throw "Unexpected type: $($property.element.type)" }
if ($property.element.isOffscreen) { throw "Counter is offscreen" }
Koperty JSON to:
-
inspect:{ "depth", "interactive", "hideDisabled", "hideOffscreen", "windows": [...] } -
search:{ "matchCount", "hasMore", "matches": [...] } -
wait-for:{ "found", "waitedMs", "element"?, "timedOut" } -
get-property:{ "elementId", "element", "properties": { ... } }
Typizowane elementy używają elementów type i liczbowych x, y, widthi height.
Geometria jest wyrażona w pikselach ekranu fizycznego.
0,0,0,0jest pusty/bez wyświetlanego prostokąta interfejsu użytkownika automatyzacja interfejsu użytkownika w tej projekcji; isOffscreen jest oddzielny, więc element offscreen może nadal mieć granice niezerowe.
Każdy inspect --jsonwindows[] wpis i status --json wynik obejmują windowDpi, (scalewindowDpi / 96), dpiAwarenessi coordinateSpace: "physical-screen-pixels". Opisują one kontekst DPI okna docelowego, a nie bezwarunkowe dpi monitora: Windows raportuje 96 dla okna nieświadomego, dpi systemu dla okna obsługującego system i bieżącego monitora DPI dla okna obsługującego monitor. Jeśli nie można odczytać kontekstu HWND lub DPI, polecenie kończy się niepowodzeniem, a nie dyskretnie podstawiając wartość 96. Gdy status rozwiąże proces przed jego oknem najwyższego poziomu, hwnd jest 0 i pola DPI zostaną pominięte do momentu, gdy okno istnieje. W przypadku całego inspectprocesu wybrane okno docelowe pozostaje w trybie fail-fast. Jeśli późniejsze okno podręczne zniknie po odczytaniu drzewa, jego windows[] wpis przenosi dpiError i pomija pola DPI, podczas gdy pozostałe drzewa okien są nadal zwracane.
Zapoznaj się z dostarczonymi winapp-ui-automation umiejętnościami references/ui-json-envelope.md , aby zapoznać się z kompletnymi przykładami każdej koperty.
Przykład pełnego testu weryfikacyjnego kompilacji
# 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