Biblioteka Python — to projekt open source do automatyzacji desktopowych aplikacji GUI na Windows. W ciągu ostatnich dwóch lat zyskał nowe, duże funkcje:
- Wsparcie technologii MS UI Automation. Interfejs pozostaje taki sam, a teraz wspiera: WinForms, WPF, Qt5, Windows Store (UWP) itd. — prawie wszystko, co istnieje na Windows.
- System backendów/pluginów (na razie są dwa pod maską: domyślny
"win32"i nowy"uia"). Płynnie przechodzimy w kierunku wsparcia dla wielu platform. - Win32 haki dla myszy i klawiatury (hot keys w stylu pyHook).
Przeprowadzimy również mały przegląd tego, co istnieje w open source do automatyzacji desktopowej (bez roszczeń do poważnego porównania).
Artykuł ten jest częściowo transkrypcją wykładu z konferencji SQA Days 20 w Mińsku ( i ), częściowo wersją polską dla pywinauto.
- Podstawowe podejścia
- Podstawowe technologie dostępu desktopowego
Zacznijmy od krótkiego przeglądu open source w tej dziedzinie. W przypadku desktopowych aplikacji GUI jest to nieco bardziej skomplikowane niż w przypadku webowych, które mają Selenium. Oto główne podejścia:
Metoda współrzędnych
Hardcodujemy punkty kliknięcia, licząc na szczęśliwe trafienia.
[+] Wiele platform, łatwe do wdrożenia.
[+] Łatwo wprowadzić nagranie-replay testów.
[-] Najmniej stabilne w zmieniających się rozdzielczościach ekranu, motywach, czcionkach, rozmiarach okien itd.
[-] Wymagana ogromna ilość pracy na wsparcie, często prościej jest wygenerować testy na nowo lub testować ręcznie.
[-] Automatyzuje tylko działania, są inne metody do weryfikacji i wydobywania danych.
Narzędzia (wieloplatformowe): , , i wielu innych. Zazwyczaj bardziej skomplikowane narzędzia zawierają tę funkcjonalność (nie zawsze wieloplatformowo).
Warto wspomnieć, że metoda współrzędnych może uzupełniać inne podejścia. Na przykład, przy niestandardowej grafice można klikać według względnych współrzędnych (od lewego górnego rogu okna/elementu, a nie całego ekranu) — zazwyczaj jest to dość niezawodne, zwłaszcza biorąc pod uwagę długość/szerokość całego elementu (wtedy różne rozdzielczości ekranu nie będą przeszkadzać).
Inna opcja: wybierać do testów tylko jedną maszynę o stabilnych ustawieniach (nie cross-platformowa, ale w niektórych przypadkach się nadaje).
Rozpoznawanie wzorców obrazów
[+] Cross-platformowy
[+-] Relatywnie niezawodny (lepszy niż metoda współrzędnych), ale wciąż wymaga pewnych sztuczek.
[-+] Relatywnie wolny, ponieważ wymaga zasobów CPU do algorytmów rozpoznawania.
[-] O rozpoznawaniu tekstu (OCR) zazwyczaj nie ma mowy => nie można uzyskać danych tekstowych. Z tego co mi wiadomo, istniejące rozwiązania OCR nie są zbyt niezawodne do tego typu zadań i nie mają szerokiego zastosowania (zapraszam do komentarzy, jeśli już tak nie jest).
Narzędzia: , (kompatybilny z Sikuli, w czystym Pythonie), .
Technologie dostępności
[+] Najbardziej niezawodna metoda, ponieważ pozwala na wyszukiwanie według tekstu, niezależnie od tego, jak jest on renderowany przez system lub framework.
[+] Pozwala na wyodrębnienie danych tekstowych => łatwiej weryfikować wyniki testów.
[+] Zazwyczaj najszybsza, ponieważ prawie w ogóle nie zużywa zasobów CPU.
[-] Trudno stworzyć narzędzie cross-platformowe: absolutnie wszystkie biblioteki open-source obsługują jedną lub dwie technologie dostępności. Windows/Linux/MacOS nie jest całkowicie wspierana przez nikogo, poza płatnymi jak TestComplete, UFT czy Squish.
[-] Nie zawsze taka technologia jest w ogóle dostępna. Na przykład testowanie ekranu załadunkowego wewnątrz VirtualBoxa — tutaj nie sposób obejść się bez rozpoznawania obrazów. Jednak w wielu klasycznych przypadkach podejście dostępności wciąż jest zastosowane. O tym będzie mowa dalej.
Narzędzia: w C#, w C# (kompatybilny z Selenium), w C# (kompatybilny z Appium), , (kompatybilny z LDTP), , w Ruby, (Linux Desktop Testing Project) i jego wersja na Windows .
LDTP — chyba jedyne cross-platformowe narzędzie open-source (a właściwie rodzina bibliotek) oparte na technologiach dostępności. Jednak nie jest zbyt popularne. Sam nie korzystałem z niego, ale według opinii interfejs nie jest najwygodniejszy. Jeśli są pozytywne opinie, proszę podzielić się nimi w komentarzach.
Testowy backdoor (znany też jako wewnętrzny rower)
Dla aplikacji wieloplatformowych sami deweloperzy często tworzą wewnętrzny mechanizm w celu zapewnienia testowalności. Na przykład, tworzą serwer TCP w aplikacji, z którym podłączają się testy i wysyłają polecenia tekstowe: co nacisnąć, skąd wziąć dane, itd. Jest to niezawodne, ale nie uniwersalne.
Podstawowe technologie dostępu desktopowego
Stary, dobry Win32 API
Większość aplikacji Windows stworzonych przed pojawieniem się WPF, a następnie Windows Store, zbudowana jest w ten czy inny sposób na Win32 API. A dokładniej, MFC, WTL, C++ Builder, Delphi, VB6 — wszystkie te narzędzia korzystają z Win32 API. Nawet Windows Forms — w dużej mierze kompatybilne z Win32 API.
Narzędzia: (podobny do VB) i Python wrapper , (własny język, ma interfejs IDispatch COM), (Python), (Ruby), (Ruby).
Microsoft UI Automation
Główny plus: technologia MS UI Automation wspiera przytłaczającą większość aplikacji GUI na Windows z rzadkimi wyjątkami. Problem: nie jest znacznie łatwiejsza do nauki niż Win32 API. W przeciwnym razie nikt by nie tworzył wrapperów nad nią.
Faktycznie to zestaw niestandardowych interfejsów COM (głównie, UIAutomationCore.dll), a także ma .NET powłokę w postaci namespace System.Windows.Automation. Zresztą ma wprowadzony błąd, przez który niektóre elementy UI mogą być pomijane. Dlatego lepiej korzystać bezpośrednio z UIAutomationCore.dll (jeśli słyszałeś o UiaComWrapper na C#, to to właśnie to).
Rodzaje interfejsów COM:
(1) Podstawowy IUknown — „węzeł wszelkiego zła”. Najniższy poziom, ani razu nie przyjazny użytkownikowi.
(2) IDispatch i pochodne (np., Excel.Application), które można używać w Pythonie za pomocą pakietu win32com.client (wchodzi w skład pyWin32). Najbardziej wygodna i estetyczna opcja.
(3) Niestandardowe interfejsy, z którymi potrafi pracować zewnętrzny pakiet Python .
Narzędzia: w C#, 0.6.0+, w C#, (ich kod źródłowy c-showych wrapperów nad UIAutomationCore.dll nie został ujawniony), na Ruby.
AT-SPI
Mimo że prawie wszystkie dystrybucje systemów Linux są oparte na X Window System (w Fedora 25 „iksy” zastąpiono Waylandem), „iksy” pozwalają na operowanie tylko oknami najwyższego poziomu oraz myszą/klawiaturą. Aby uzyskać szczegółową analizę przycisków, list boxów i tak dalej — istnieje technologia AT-SPI. Najpopularniejsze menedżery okien mają tzw. demon rejestru AT-SPI, który zapewnia aplikacjom automatyzowany GUI (przynajmniej wspierane są Qt i GTK).
Narzędzia: .
pyatspi2, moim zdaniem, zawiera zbyt wiele zależności typu tego samego PyGObject. Sama technologia jest dostępna w postaci zwykłej dynamicznej biblioteki libatspi.so. Jest do niej dostęp. . Dla biblioteki pywinauto planujemy wdrożyć wsparcie AT-SPI dokładnie w ten sposób: poprzez załadowanie libatspi.so i moduł ctypes. Jest tylko mały problem z używaniem odpowiedniej wersji, ponieważ dla aplikacji GTK+ i Qt są one nieco różne. Możliwy wydanie pywinauto 0.7.0 z pełnym wsparciem dla systemu Linux można się spodziewać w pierwszej połowie 2018 roku.
Apple Accessibility API
Na MacOS istnieje własny język automatyzacji AppleScript. Aby zrealizować coś podobnego w Pythonie, oczywiście trzeba użyć funkcji z ObjectiveC. Od MacOS 10.6 w preinstalowanym pythonie zawarty jest pakiet pyobjc. To również ułatwi listę zależności w przyszłym wsparciu dla pywinauto.
Narzędzia: Oprócz języka Apple Script, warto zwrócić uwagę na , znany również jako pyatom. Jest zgodny z interfejsem LDTP, ale jest również samodzielną biblioteką. Istnieje jego , napisany przez mojego studenta. Jest znany problem: nie działają elastyczne czasy oczekiwania (metody waitFor*). Ale ogólnie rzecz biorąc, to całkiem niezła rzecz.
Jak rozpocząć pracę z pywinauto
Na początku warto zaopatrzyć się w inspektora obiektów GUI (to, co nazywają narzędziem szpiegowskim). Pomoże to zbadać aplikację od wewnątrz: jak zbudowana jest hierarchia elementów, jakie właściwości są dostępne. Najbardziej znane inspektory obiektów to:
- Spy++ — wchodzi w skład Visual Studio, w tym wersji Express lub Community Edition. Używa Win32 API. Znany jest też jego klon AutoIt Window Info.
- Inspect.exe — wchodzi w skład Windows SDK. Jeśli jest zainstalowany, można go znaleźć w folderze
C:Program Files (x86)Windows Kitsbinx64. W samym inspektorze należy wybrać tryb UI Automation zamiast MS AA (Active Accessibility, przodek UI Automation).
Prześwietlając aplikację na wylot, wybieramy backend, który będziemy używać. Wystarczy podać nazwę backendu przy tworzeniu obiektu Application.
- backend=»win32″ — jest to obecnie domyślnie używane, dobrze współpracuje z MFC, WTL, VB6 i innymi aplikacjami starszej generacji.
- backend=»uia» — nowy backend dla MS UI Automation: idealnie działa z WPF i WinForms; również dobry dla aplikacji Delphi i Windows Store; działa z Qt5 i niektórymi aplikacjami Java. Ogólnie rzecz biorąc, jeśli Inspect.exe widzi elementy i ich właściwości, to oznacza, że ten backend jest odpowiedni. W zasadzie większość przeglądarek również wspiera UI Automation (Mozilla domyślnie, a Chrome podczas uruchamiania musi otrzymać przekazany klucz wiersza poleceń
--force-renderer-accessibility, aby zobaczyć elementy na stronach w Inspect.exe). Oczywiście, konkurencja z Selenium w tej dziedzinie raczej nie ma szans. Po prostu kolejny sposób pracy z przeglądarką (może się przydać w scenariuszu międzyproduktowym).
Punkty początkowe dla automatyzacji
Aplikacja jest dobrze zrozumiana. Czas stworzyć obiekt Application i go uruchomić lub dołączyć do już działającego. To nie jest zwykły klon standardowej klasy subprocess.Popen, a dokładniej wprowadzający obiekt, który ogranicza wszystkie Twoje działania granicami procesu. To bardzo przydatne, jeśli uruchomionych jest kilka instancji aplikacji, a nie chcemy ich ruszać.
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# Opisujemy okno, które chcemy znaleźć w procesie Notepad.exe
dlg_spec = app.UntitledNotepad
# czekamy, aż okno rzeczywiście się pojawi
actionable_dlg = dlg_spec.wait('visible')Jeśli chcesz zarządzać od razu kilkoma aplikacjami, pomoże Ci klasa Desktop. Na przykład, w kalkulatorze na Win10 hierarchia elementów rozciąga się aż na kilka procesów (nie tylko calc.exe). Więc bez obiektu Desktop się nie obędzie.
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')Obiekt główny (Application lub Desktop) — to jedyne miejsce, w którym trzeba określić backend. Wszystko inne przejrzysto wpisuje się w koncepcję „specyfikacja->wrapper”, o której dalej.
Specyfikacje okien/elementów
To podstawowa koncepcja, na której opiera się interfejs pywinauto. Możesz opisać okno/element w sposób przybliżony lub bardziej szczegółowy, nawet jeśli jeszcze nie istnieje lub już jest zamknięte. Specyfikacja okna (obiekt WindowSpecification) przechowuje kryteria, według których należy szukać rzeczywistego okna lub elementu.
Przykład szczegółowej specyfikacji okna:
>>> dlg_spec = app.window(title='Untitled - Notepad')
>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>
>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>Samo wyszukiwanie okna odbywa się poprzez wywołanie metody .wrapper_object(). Zwraca pewien „wrapper” dla rzeczywistego okna/elementu lub rzuca ElementNotFoundError (czasami ElementAmbiguousError, jeśli znaleziono kilka elementów, to znaczy, że należy doprecyzować kryteria wyszukiwania). Ten „wrapper” już potrafi robić jakieś działania z elementem lub pobierać z niego dane.
Python może ukrywać wywołanie .wrapper_object(), tak że końcowy kod staje się krótszy. Zalecamy używać go tylko do debugowania. Następujące dwie linijki robią dokładnie to samo:
dlg_spec.wrapper_object().minimize() # debugowanie
dlg_spec.minimize() # produkcjaIstnieje wiele kryteriów wyszukiwania dla specyfikacji okna. Oto kilka przykładów:
# могут иметь несколько уровней
app.window(title_re='.* - Notepad$').window(class_name='Edit')
# можно комбинировать критерии (как AND) и не ограничиваться одним процессом приложения
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')Lista wszystkich możliwych kryteriów znajduje się w dokumentacji funkcji .
Magia dostępu według atrybutu i klucza
Python ułatwia tworzenie specyfikacji okna i dynamicznie rozpoznaje atrybuty obiektu (metoda została nadpisana __getattribute__). Oczywiście na nazwę atrybutu nakładane są te same ograniczenia, co na nazwę każdej zmiennej (nie można wstawiać spacji, przecinków i innych znaków specjalnych). Na szczęście, pywinauto używa tzw. algorytmu „najlepszego dopasowania”, który jest odporny na literówki i niewielkie wariacje.
app.UntitledNotepad
# to samo, co
app.window(best_match='UntitledNotepad')Jeśli potrzebne są ciągi Unicode (na przykład dla języka rosyjskiego), spacje itd., można uzyskać dostęp za pomocą klucza (jakby to był zwykły słownik):
app['Untitled - Notepad']
# to samo, co
app.window(best_match='Untitled - Notepad')Pięć zasad dla magicznych nazw
Jak poznać wzorcowe magiczne nazwy? Te, które są przypisywane elementowi przed wyszukiwaniem. Jeśli podałeś nazwę, wystarczająco podobną do wzorca, element zostanie znaleziony.
- Po tytule (tekst, nazwa):
app.Properties.OK.click() - Według tekstu i typu elementu:
app.Properties.OKButton.click() - Według typu i numeru:
app.Properties.Button3.click()(nazwyButton0iButton1są przypisane do pierwszego znalezionego elementu,Button2— do drugiego, a dalej według porządku — tak historycznie się ukształtowało) - Według statycznego tekstu (po stronie lub u góry) i typu:
app.OpenDialog.FileNameEdit.set_text("")(przydatne dla elementów z dynamicznym tekstem) - Według typu i tekstu wewnętrznego:
app.Properties.TabControlSharing.select("General")
Zazwyczaj stosuje się jednocześnie dwa lub trzy zasady, rzadko więcej. Aby sprawdzić, jakie konkretne nazwy są dostępne dla każdego elementu, można użyć metody print_control_identifiers(). Może ona wydrukować drzewo elementów zarówno na ekranie, jak i w pliku. Dla każdego elementu drukowane są jego wzorcowe magiczne nazwy. Można także skopiować stamtąd bardziej szczegółowe specyfikacje elementów podrzędnych. Wynik w skrypcie będzie wyglądał tak:
app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")Sam konar elementów — zazwyczaj dość duża struktura.
>>> app.Properties.print_control_identifiers()
Identifikatory kontrolne:
Dialog - 'Właściwości systemu Windows NT' (L688, T518, R1065, B1006)
[u'Właściwości systemu Windows NTDialog', u'Dialog', u'Właściwości systemu Windows NT']
child_window(title="Właściwości systemu Windows NT", control_type="Window")
|
| Obraz - '' (L717, T589, R749, B622)
| [u'', u'0', u'Obraz1', u'Obraz0', 'Obraz', u'1']
| child_window(auto_id="13057", control_type="Image")
|
| Obraz - '' (L717, T630, R1035, B632)
| ['Obraz2', u'2']
| child_window(auto_id="13095", control_type="Image")
|
| Edytuj - 'Nazwa folderu:' (L790, T596, R1036, B619)
| [u'3', 'Edit', u'Edit1', u'Edit0']
| child_window(title="Nazwa folderu:", auto_id="13156", control_type="Edit")
|
| Statyczny - 'Typ:' (L717, T643, R780, B658)
| [u'Typ:Statyczny', u'Statyczny', u'Statyczny1', u'Statyczny0', u'Typ:']
| child_window(title="Typ:", auto_id="13080", control_type="Text")
|
| Edytuj - 'Typ:' (L790, T643, R1036, B666)
| [u'4', 'Edit2', u'Typ:Edytuj']
| child_window(title="Typ:", auto_id="13059", control_type="Edit")
|
| Statyczny - 'Lokalizacja:' (L717, T669, R780, B684)
| [u'Lokalizacja:Statyczny', u'Lokalizacja:', u'Statyczny2']
| child_window(title="Lokalizacja:", auto_id="13089", control_type="Text")
|
| Edytuj - 'Lokalizacja:' (L790, T669, R1036, B692)
| ['Edit3', u'Lokalizacja:Edytuj', u'5']
| child_window(title="Lokalizacja:", auto_id="13065", control_type="Edit")
|
| Statyczny - 'Rozmiar:' (L717, T695, R780, B710)
| [u'Rozmiar:Statyczny', u'Rozmiar:', u'Statyczny3']
| child_window(title="Rozmiar:", auto_id="13081", control_type="Text")
|
| Edytuj - 'Rozmiar:' (L790, T695, R1036, B718)
| ['Edit4', u'6', u'Rozmiar:Edytuj']
| child_window(title="Rozmiar:", auto_id="13064", control_type="Edit")
|
| Statyczny - 'Rozmiar na dysku:' (L717, T721, R780, B736)
| [u'Rozmiar na dysku:', u'Rozmiar na dysku:Statyczny', u'Statyczny4']
| child_window(title="Rozmiar na dysku:", auto_id="13107", control_type="Text")
|
| Edytuj - 'Rozmiar na dysku:' (L790, T721, R1036, B744)
| ['Edit5', u'7', u'Rozmiar na dysku:Edytuj']
| child_window(title="Rozmiar na dysku:", auto_id="13106", control_type="Edit")
|
| Statyczny - 'Zawiera:' (L717, T747, R780, B762)
| [u'Zawiera:1', u'Zawiera:0', u'Zawiera:Statyczny', u'Statyczny5', u'Zawiera:']
| child_window(title="Zawiera:", auto_id="13088", control_type="Text")
|
| Edytuj - 'Zawiera:' (L790, T747, R1036, B770)
| [u'8', 'Edit6', u'Zawiera:Edytuj']
| child_window(title="Zawiera:", auto_id="13087", control_type="Edit")
|
| Obraz - 'Zawiera:' (L717, T773, R1035, B775)
| [u'Zawiera:Obraz', 'Obraz3', u'Zawiera:2']
| child_window(title="Zawiera:", auto_id="13096", control_type="Image")
|
| Statyczny - 'Utworzono:' (L717, T786, R780, B801)
| [u'Utworzono:', u'Utworzono:Statyczny', u'Statyczny6', u'Utworzono:1', u'Utworzono:0']
| child_window(title="Utworzono:", auto_id="13092", control_type="Text")
|
| Edytuj - 'Utworzono:' (L790, T786, R1036, B809)
| [u'Utworzono:Edytuj', 'Edit7', u'9']
| child_window(title="Utworzono:", auto_id="13072", control_type="Edit")
|
| Obraz - 'Utworzono:' (L717, T812, R1035, B814)
| [u'Utworzono:Obraz', 'Obraz4', u'Utworzono:2']
| child_window(title="Utworzono:", auto_id="13097", control_type="Image")
|
| Statyczny - 'Atrybuty:' (L717, T825, R780, B840)
| [u'Atrybuty:Statyczny', u'Statyczny7', u'Atrybuty:']
| child_window(title="Atrybuty:", auto_id="13091", control_type="Text")
|
| Pole wyboru - 'Tylko do odczytu (dotyczy tylko plików w folderze)' (L790, T825, R1035, B841)
| [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Tylko do odczytu (dotyczy tylko plików w folderze)CheckBox', u'Tylko do odczytu (dotyczy tylko plików w folderze)']
| child_window(title="Tylko do odczytu (dotyczy tylko plików w folderze)", auto_id="13075", control_type="CheckBox")
|
| Pole wyboru - 'Ukryty' (L790, T848, R865, B864)
| ['CheckBox2', u'UkrytyCheckBox', u'Ukryty']
| child_window(title="Ukryty", auto_id="13076", control_type="CheckBox")
|
| Przycisk - 'Zaawansowane...' (L930, T845, R1035, B868)
| [u'Zaawansowane...', u'Zaawansowane...Button', 'Button', u'Button1', u'Button0']
| child_window(title="Zaawansowane...", auto_id="13154", control_type="Button")
|
| Przycisk - 'OK' (L814, T968, R889, B991)
| ['Button2', u'OK', u'OKButton']
| child_window(title="OK", auto_id="1", control_type="Button")
|
| Przycisk - 'Anuluj' (L895, T968, R970, B991)
| ['Button3', u'CancelButton', u'Anuluj']
| child_window(title="Anuluj", auto_id="2", control_type="Button")
|
| Przycisk - 'Zastosuj' (L976, T968, R1051, B991)
| ['Button4', u'ApplyButton', u'Zastosuj']
| child_window(title="Zastosuj", auto_id="12321", control_type="Button")
|
| TabControl - '' (L702, T556, R1051, B962)
| [u'10', u'TabControlSharing', u'TabControlWersje poprzednie', u'TabControlBezpieczeństwo', u'TabControl', u'TabControlDostosuj']
| child_window(auto_id="12320", control_type="Tab")
| |
| | TabItem - 'Ogólne' (L704, T558, R753, B576)
| | [u'OgólneTabItem', 'TabItem', u'Ogólne', u'TabItem0', u'TabItem1']
| | child_window(title="Ogólne", control_type="TabItem")
| |
| | TabItem - 'Udostępnianie' (L753, T558, R801, B576)
| | [u'Udostępnianie', u'UdostępnianieTabItem', 'TabItem2']
| | child_window(title="Udostępnianie", control_type="TabItem")
| |
| | TabItem - 'Bezpieczeństwo' (L801, T558, R851, B576)
| | [u'Bezpieczeństwo', 'TabItem3', u'BezpieczeństwoTabItem']
| | child_window(title="Bezpieczeństwo", control_type="TabItem")
| |
| | TabItem - 'Wersje poprzednie' (L851, T558, R947, B576)
| | [u'Wersje poprzednieTabItem', u'Wersje poprzednie', 'TabItem4']
| | child_window(title="Wersje poprzednie", control_type="TabItem")
| |
| | TabItem - 'Dostosuj' (L947, T558, R1007, B576)
| | [u'DostosujTabItem', 'TabItem5', u'Dostosuj']
| | child_window(title="Dostosuj", control_type="TabItem")
|
| Pasek tytułu - 'Brak' (L712, T521, R1057, B549)
| ['Pasek tytułu', u'11']
| |
| | Menu - 'System' (L696, T526, R718, B548)
| | [u'System0', u'System', u'System1', u'Menu', u'SystemMenu']
| | child_window(title="System", auto_id="MenuBar", control_type="MenuBar")
| | |
| | | Element menu - 'System' (L696, T526, R718, B548)
| | | [u'System2', u'MenuItem', u'SystemMenuItem']
| | | child_window(title="System", control_type="MenuItem")
| |
| | Przycisk - 'Zamknij' (L1024, T519, R1058, B549)
| | [u'CloseButton', u'Zamknij', 'Button5']
| | child_window(title="Zamknij", control_type="Button")W niektórych przypadkach drukowanie całego drzewa może być opóźnione (na przykład w iTunes na jednej karcie aż trzy tysiące elementów!), ale można użyć parametru depth głębokość): depth=1 — sam element, depth=2 — tylko bezpośrednie dzieci, i tak dalej. Można go również podać w specyfikacjach przy tworzeniu child_window.
Przykłady
Nieustannie uzupełniamy . Z nowszych warto wspomnieć o automatyzacji analizatora sieciowego WireShark (to dobry przykład aplikacji Qt5; chociaż tę zadanie można rozwiązać i bez GUI, ponieważ istnieje scapy.Sniffer z pakietu Pythona ). Jest również przykład automatyzacji MS Paint z jego paskiem narzędzi Ribbon.
Jeszcze jeden świetny przykład, napisany przez mojego studenta: (przeniesie się do głównego repozytorium nieco później).
I oczywiście przykład subskrypcji zdarzeń klawiatury (hot keys) i myszy:
.
Podziękowania
Szczególne podziękowania dla tych, którzy nieustannie pomagają rozwijać projekt. Dla mnie i to stałe hobby. Dwóch moich studentów z NNGU niedawno obroniło tytuły licencjackie na ten temat. wniósł duży wkład w wsparcie MS UI Automation i niedawno zaczął tworzyć automatyczny generator kodu w oparciu o zasady „nagrywania-odtwarzania” na podstawie właściwości tekstowych (to jest najbardziej skomplikowana funkcja), na razie tylko dla backendu „uia”. opracowuje nowy backend pod Linuxa oparty na AT-SPI (moduły mouse i keyboard na podstawie — już w wydaniach 0.6.x).
Ponieważ od dłuższego czasu prowadzę specjalny kurs automatyzacji w Pythonie, część studentów magisterskich wykonuje prace domowe, implementując niewielkie funkcje lub przykłady automatyzacji. Niektóre kluczowe rzeczy na etapie badań również kiedyś wykopali właśnie studenci. Choć czasami trzeba uważnie pilnować jakości kodu. W tym mocno pomagają statyczne analizatory (QuantifiedCode, Codacy i Landscape) oraz automatyczne testy w chmurze (usługa AppVeyor) z pokryciem kodu na poziomie około 95%.
Również dziękuję wszystkim, którzy zostawiają opinie, zgłaszają błędy i wysyłają pull requesty!
Dodatkowe zasoby
Śledzimy pytania po (niedawno pojawił się ) i . Jest .
Każdego miesiąca aktualizujemy . Pod względem liczby gwiazdek na GitHubie, szybciej rośnie tylko Autohotkey (mają bardzo dużą społeczność i długą historię) oraz PyAutoGUI (w dużej mierze dzięki popularności książek jej autora Al Sweigarta: „Automate the Boring Stuff with Python” i innych).
Źródło: habr.com
