Zautomatyzuj GUI pulpitu w Pythonie + pywinauto: jak zaprzyjaźnić się z MS UI Automation

Biblioteka Pythona pywinauto — jest projektem typu open source służącym do automatyzacji aplikacji graficznych na komputerach stacjonarnych WindowsW ciągu ostatnich dwóch lat dodano nowe, ważne funkcje:

  • Wsparcie dla technologii MS UI Automation. Interfejs pozostaje ten sam i teraz obsługuje WinForms, WPF, Qt5, Windows Sklep (UWP) i tak dalej - prawie wszystko, co jest na Windows.
  • System backendów/wtyczek (obecnie pod maską znajdują się dwa z nich: default "win32" i nowe "uia"). Następnie płynnie przechodzimy w stronę cross-platformowości.
  • Haki Win32 dla myszy i klawiatury (klawisze skrótu w duchu pyHook).

Podamy również krótki przegląd tego, co jest dostępne w otwartym kodzie źródłowym do automatyzacji komputerów stacjonarnych (bez udawania poważnego porównania).

Artykuł ten jest częściowym zapisem relacji z konferencji SQA Days 20 w Mińsku (nagranie wideo и slajdy), częściowo wersja rosyjska Przewodnik wprowadzający dla pywinauto.

Zacznijmy od krótkiego przeglądu open source w tym obszarze. W przypadku aplikacji GUI na komputery stacjonarne wszystko jest nieco bardziej skomplikowane niż w przypadku sieci, która ma Selenium. Oto główne podejścia:

metoda współrzędnych

Zakoduj na stałe punkty kliknięcia, mamy nadzieję na udane trafienia.
[+] Wieloplatformowy, łatwy do wdrożenia.
[+] Łatwe „nagrywanie i odtwarzanie” nagrań testowych.
[-] Najbardziej niestabilny przy zmianie rozdzielczości ekranu, motywu, czcionek, rozmiarów okien itp.
[-] Wymagane są ogromne wysiłki w zakresie wsparcia; często łatwiej jest zregenerować testy od zera lub przetestować ręcznie.
[-] Automatyzuje tylko działania; istnieją inne metody weryfikacji i odzyskiwania danych.

Narzędzia (wieloplatformowe): autopsja, PyAutoGUI, Dane wejściowe PyUser i wiele innych. Zazwyczaj bardziej złożone narzędzia obejmują tę funkcjonalność (nie zawsze wieloplatformowe).

Warto powiedzieć, że metoda współrzędnych może uzupełniać inne podejścia. Przykładowo w przypadku niestandardowej grafiki można kliknąć na współrzędne względne (od lewego górnego rogu okna/elementu, a nie całego ekranu) - zwykle jest to całkiem niezawodne, szczególnie jeśli weźmie się pod uwagę długość/szerokość cały element (wtedy różne rozdzielczości ekranu nie zaszkodzą).

Inna opcja: przydziel do testów tylko jedną maszynę ze stabilnymi ustawieniami (nie na wielu platformach, ale w niektórych przypadkach jest to dobre).

Rozpoznawanie obrazu referencyjnego

[+] Wieloplatformowe
[+-] Stosunkowo niezawodna (lepsza niż metoda współrzędnych), ale nadal wymaga pewnych sztuczek.
[-+] Stosunkowo wolno, ponieważ Wymaga zasobów procesora dla algorytmów rozpoznawania.
[-] Rozpoznawanie tekstu (OCR) z reguły nie wchodzi w grę => nie można uzyskać danych tekstowych. Z tego co wiem, istniejące rozwiązania OCR nie są zbyt niezawodne w przypadku tego typu zadań i nie są powszechnie stosowane (proszę o komentarze, jeśli jeszcze tak nie jest).

Narzędzia: Sikuli, Lokaj (kompatybilny z Sikuli, czysty Python), PyAutoGUI.

Technologia dostępności

[+] Najbardziej niezawodna metoda, ponieważ umożliwia wyszukiwanie według tekstu, niezależnie od tego, jak jest on renderowany przez system lub framework.
[+] Umożliwia wyodrębnienie danych tekstowych => łatwiejsza weryfikacja wyników testów.
[+] Z reguły najszybciej, bo prawie nie zużywa zasobów procesora.
[-] Trudno jest stworzyć narzędzie wieloplatformowe: absolutnie wszystkie biblioteki typu open source obsługują jedną lub dwie technologie ułatwień dostępu. Windows/Linux/MacOS nie jest w pełni obsługiwany przez nikogo poza płatnymi aplikacjami, takimi jak TestComplete, UFT i Squish.
[-] Taka technologia nie zawsze jest w zasadzie dostępna. Na przykład testowanie ekranu ładowania w VirtualBoxie - nie da się tego zrobić bez rozpoznawania obrazu. Jednak w wielu klasycznych przypadkach podejście dotyczące dostępności nadal ma zastosowanie. Zostanie to omówione dalej.

Narzędzia: TestStack.Biały w języku C#, Winium.Pulpit w C# (kompatybilny z Selenium), Sterownik MS WinApp w języku C# (kompatybilny z Appium), pywinauto, pyatom (kompatybilny z LDTP) Python-UIAutomation-dla-Windows, RAutomatyzacja w Rubinie, LDTP (Linux Projekt testowania pulpitu) i jego Windows wersja Kobra.

LDTP jest prawdopodobnie jedynym wieloplatformowym narzędziem typu open source (dokładniej rodziną bibliotek) opartym na technologiach dostępności. Jednak nie jest to zbyt popularne. Sam z niego nie korzystałem, ale według opinii jego interfejs nie jest najwygodniejszy. Jeśli masz pozytywne opinie, podziel się nimi w komentarzach.

Przetestuj tylne drzwi (inaczej rower stacjonarny)

W przypadku aplikacji wieloplatformowych programiści często sami tworzą wewnętrzny mechanizm zapewniający testowalność. Przykładowo tworzą w aplikacji serwer usług TCP, testują połączenie z nim i wysyłają polecenia tekstowe: w co kliknąć, skąd wziąć dane itp. Niezawodny, ale nie uniwersalny.

Podstawowe technologie dostępności komputerów stacjonarnych

Stary, dobry interfejs API Win32

Najbardziej Windows aplikacje napisane przed wydaniem WPF i po nim Windows Sklepy są w taki czy inny sposób zbudowane na API Win32. Mianowicie MFC, WTL, C++ Builder, Delphi, VB6 – wszystkie te narzędzia korzystają z API Win32. Nawet Windows Formularze są w dużej mierze zgodne z API Win32.

Narzędzia: AutoIt (podobnie jak VB) i opakowanie Pythona pyautoit, AutoHotkey (własny język, istnieje interfejs IDispatch COM), pywinauto (Pyton) RAutomatyzacja (Rubin) win32-autogui (Rubin).

Automatyzacja interfejsu użytkownika Microsoftu

Główna zaleta: technologia automatyzacji interfejsu użytkownika MS obsługuje zdecydowaną większość aplikacji GUI Windows Z nielicznymi wyjątkami. Problem: nie jest o wiele łatwiejszy do nauczenia niż API Win32. W przeciwnym razie nikt nie tworzyłby dla niego wrapperów.

W rzeczywistości jest to zestaw niestandardowych interfejsów COM (głównie UIAutomationCore.dll), a także ma opakowanie .NET w postaci namespace System.Windows.Automation. Nawiasem mówiąc, ma wprowadzony błąd, przez który niektóre elementy interfejsu użytkownika mogą zostać pominięte. Dlatego lepiej jest używać bezpośrednio UIAutomationCore.dll (jeśli słyszałeś o UiaComWrapper w C#, to właśnie to).

Rodzaje interfejsów COM:

(1) Podstawowe IUznane – „korzeń wszelkiego zła”. Najniższy poziom, nigdy przyjazny dla użytkownika.
(2) IDispatch i instrumenty pochodne (np Excel.Application), którego można używać w Pythonie za pomocą pakietu win32com.client (dołączonego do pyWin32). Najwygodniejsza i najpiękniejsza opcja.
(3) Niestandardowe interfejsy, z którymi może współpracować pakiet Pythona innej firmy współrodzajów.

Narzędzia: TestStack.Biały w języku C#, pywinauto 0.6.0 +, Winium.Pulpit w języku C#, Python-UIAutomation-dla-Windows (ich kod źródłowy opakowań C dla UIAutomationCore.dll nie został ujawniony), RAutomatyzacja w Rubinie.

AT-SPI

Pomimo tego, że prawie wszystkie osie rodziny Linux Zbudowany na systemie X Window (w Fedorze 25 „X” został zastąpiony przez Wayland), „X” pozwala jedynie na obsługę okien najwyższego poziomu oraz myszy i klawiatury. Do szczegółowej analizy przycisków, list rozwijanych itp. dostępna jest technologia AT-SPI. Najpopularniejsze menedżery okien posiadają tzw. demona rejestru AT-SPI, który zapewnia zautomatyzowany graficzny interfejs użytkownika dla aplikacji (przynajmniej Qt i GTK są obsługiwane).

Narzędzia: pyatspi2.

Moim zdaniem pyatspi2 zawiera zbyt wiele zależności, takich jak PyGObject. Sama technologia jest dostępna jako zwykła biblioteka dynamiczna libatspi.so. Tam jest Instrukcja obsługiW bibliotece pywinauto planujemy zaimplementować obsługę AT-SPI w ten sposób: poprzez załadowanie pliku libatspi.so i modułu ctypes. Jedynym drobnym problemem jest użycie odpowiedniej wersji, ponieważ różnią się one nieznacznie dla aplikacji GTK+ i Qt. Prawdopodobna wersja pywinauto 0.7.0 będzie oferować pełną obsługę. Linux można spodziewać się w pierwszej połowie 2018 roku.

Interfejs API ułatwień dostępu Apple

MacOS ma swój własny język automatyzacji, AppleScript. Aby zaimplementować coś takiego w Pythonie, musisz oczywiście użyć funkcji z ObjectiveC. Wygląda na to, że począwszy od systemu MacOS 10.6 pakiet pyobjc jest zawarty w preinstalowanym języku Python. Ułatwi to również wyświetlenie listy zależności do przyszłej obsługi w pywinauto.

Narzędzia: Oprócz języka Apple Script warto zwrócić na niego uwagę ATOMak, czyli pyatom. Jest interfejsem kompatybilnym z LDTP, ale jest także samodzielną biblioteką. To ma przykład automatyzacji iTunes na macO, napisany przez mojego ucznia. Istnieje znany problem: elastyczne czasy nie działają (methods waitFor*). Ale ogólnie nie jest źle.

Jak rozpocząć pracę z pywinauto

Pierwszym krokiem jest uzbrojenie się w inspektora obiektów GUI (tak zwane narzędzie szpiegowskie). Pomoże Ci to przestudiować aplikację od środka: jak zbudowana jest hierarchia elementów, jakie właściwości są dostępne. Najbardziej znani inspektorzy budowy:

  • Szpieg++ - dołączone do Visual Studio, w tym Express lub Community Edition. Korzysta z API Win32. Znany jest również jego klon Informacje o oknie AutoIt.
  • Sprawdź.exe — jest wliczony w Windows SDK. Jeśli masz go zainstalowanego, to jest on na systemie 64-bitowym. Windows Znajdziesz to w folderze C:Program Files (x86)Windows Kits<winver>binx64. W samym inspektorze musisz wybrać tryb Automatyzacja interfejsu użytkownika zamiast MS AA (aktywna dostępność, przodek automatyzacji interfejsu użytkownika).

Po dokładnym zbadaniu aplikacji wybieramy backend, z którego będziemy korzystać. Wystarczy podczas tworzenia obiektu Application podać nazwę backendu.

  • backend=”win32″ — gdy jest używany domyślnie, dobrze współpracuje z MFC, WTL, VB6 i innymi starszymi aplikacjami.
  • backend=”uia” — nowy back-end dla automatyzacji interfejsu użytkownika MS: doskonale współpracuje z WPF i WinForms; dobrze współpracuje również z Delphi i Windows Aplikacje sklepowe; współpracuje z Qt5 i niektórymi aplikacjami Java. I generalnie, jeśli Inspect.exe widzi elementy i ich właściwości, to ten backend jest odpowiedni. Zasadniczo większość przeglądarek obsługuje również automatyzację interfejsu użytkownika (domyślnie Mozilla, a Chrome wymaga przełącznika wiersza poleceń po uruchomieniu). --force-renderer-accessibilityaby zobaczyć elementy na stronach w Inspect.exe). Oczywiście konkurencja z Selenium w tym obszarze jest prawie niemożliwa. To po prostu inny sposób pracy z przeglądarką (może być przydatny w scenariuszu obejmującym wiele produktów).

Punkty wejścia do automatyzacji

Aplikacja została szczegółowo zbadana. Czas utworzyć obiekt Application i uruchomić go lub dołączyć do już działającego. To nie jest tylko klon standardowej klasy subprocess.Popen, czyli obiekt wejściowy, który ogranicza wszystkie twoje działania do granic procesu. Jest to bardzo przydatne, jeśli uruchomionych jest kilka instancji aplikacji, ale nie chcesz dotykać reszty.

from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')

# Опишем окно, которое хотим найти в процессе Notepad.exe
dlg_spec = app.UntitledNotepad
# ждем пока окно реально появится
actionable_dlg = dlg_spec.wait('visible')

Jeśli chcesz zarządzać kilkoma aplikacjami jednocześnie, te zajęcia Ci w tym pomogą Desktop. Na przykład w kalkulatorze na Win10 hierarchia elementów jest rozłożona na kilka procesów (nie tylko calc.exe). Więc nie ma obiektu Desktop niewystarczająco.

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 musisz określić backend. Wszystko inne w sposób przejrzysty mieści się w koncepcji „specyfikacja->opakowanie”, która zostanie omówiona później.

Specyfikacje okien/elementów

Jest to podstawowa koncepcja, na której zbudowany jest interfejs pywinauto. Możesz opisać okno/element z grubsza lub bardziej szczegółowo, nawet jeśli jeszcze nie istnieje lub jest już zamknięte. Specyfikacja okna (obiekt Specyfikacja okna) przechowuje kryteria wyszukiwania prawdziwego 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 przeszukiwanie okna następuje poprzez wywołanie metody .wrapper_object(). Zwraca pewne „opakowanie” dla prawdziwego okna/elementu lub rzutów ElementNotFoundError (czasami ElementAmbiguousError, jeśli znaleziono kilka elementów, czyli trzeba doprecyzować kryterium wyszukiwania). Ten „opakowanie” już wie, jak wykonać pewne akcje z elementem lub odebrać z niego dane.

Python może ukryć połączenie .wrapper_object(), więc ostateczny kod staje się krótszy. Zalecamy używanie go wyłącznie do celów debugowania. Następne dwie linie robią dokładnie to samo:

dlg_spec.wrapper_object().minimize() # debugging
dlg_spec.minimize() # production

Istnieje wiele kryteriów wyszukiwania specyfikacji okna. Oto tylko 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 pywinauto.findwindows.find_elements(…).

Magia dostępu według atrybutu i klucza

Python ułatwia tworzenie specyfikacji okien i dynamiczne rozpoznawanie atrybutów obiektów (wewnętrznie metoda jest nadpisywana __getattribute__). Oczywiście na nazwę atrybutu nałożone są te same ograniczenia, co na nazwę dowolnej zmiennej (nie można wstawiać spacji, przecinków ani innych znaków specjalnych). Na szczęście pywinauto korzysta z tak zwanego algorytmu wyszukiwania „najlepszego dopasowania”, który jest odporny na literówki i niewielkie różnice.

app.UntitledNotepad
# то же самое, что
app.window(best_match='UntitledNotepad')

Jeśli nadal potrzebujesz ciągów znaków Unicode (na przykład dla języka rosyjskiego), spacji itp., możesz uzyskać dostęp za pomocą klucza (jak gdyby był to zwykły słownik):

app['Untitled - Notepad']
# то же самое, что
app.window(best_match='Untitled - Notepad')

Pięć zasad magicznych imion

Jak znaleźć standardowe magiczne imiona? Te, które są przypisane do elementu przed wyszukiwaniem. Jeśli podałeś nazwę wystarczająco zbliżoną do standardu, to element zostanie znaleziony.

  1. Według tytułu (tekst, imię): app.Properties.OK.click()
  2. Według tekstu i typu elementu: app.Properties.OKButton.click()
  3. Według rodzaju i numeru: app.Properties.Button3.click() (nazwy Button0 и Button1 powiązany z pierwszym znalezionym elementem, Button2 - do drugiego, a potem w kolejności - tak to się działo historycznie)
  4. Według tekstu statycznego (po lewej lub u góry) i według typu: app.OpenDialog.FileNameEdit.set_text("") (przydatne w przypadku elementów z tekstem dynamicznym)
  5. Według typu i tekstu w środku: app.Properties.TabControlSharing.select("General")

Zwykle stosuje się dwie lub trzy zasady jednocześnie, rzadko więcej. Aby sprawdzić, jakie konkretne nazwy są dostępne dla każdego elementu, możesz skorzystać z metody print_control_identifiers(). Potrafi wydrukować drzewo elementów zarówno na ekran, jak i do pliku. Dla każdego elementu drukowane są jego standardowe magiczne nazwy. Możesz także skopiować i wkleić stamtąd bardziej szczegółowe specyfikacje elementów podrzędnych. Wynik w skrypcie będzie wyglądał następująco:

app.Properties.child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")

Samo drzewo żywiołów to zazwyczaj dość duży obrus.

>>> app.Properties.print_control_identifiers()

Control Identifiers:

Dialog - 'Windows NT Properties'    (L688, T518, R1065, B1006)
[u'Windows NT PropertiesDialog', u'Dialog', u'Windows NT Properties']
child_window(data-gt-translate-attributes='["title"]' title="Windows NT Properties", control_type="Window")
   |
   | Image - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Image1', u'Image0', 'Image', u'1']
   | child_window(auto_id="13057", control_type="Image")
   |
   | Image - ''    (L717, T630, R1035, B632)
   | ['Image2', u'2']
   | child_window(auto_id="13095", control_type="Image")
   |
   | Edit - 'Folder name:'    (L790, T596, R1036, B619)
   | [u'3', 'Edit', u'Edit1', u'Edit0']
   | child_window(data-gt-translate-attributes='["title"]' title="Folder name:", auto_id="13156", control_type="Edit")
   |
   | Static - 'Type:'    (L717, T643, R780, B658)
   | [u'Type:Static', u'Static', u'Static1', u'Static0', u'Type:']
   | child_window(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13080", control_type="Text")
   |
   | Edit - 'Type:'    (L790, T643, R1036, B666)
   | [u'4', 'Edit2', u'Type:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13059", control_type="Edit")
   |
   | Static - 'Location:'    (L717, T669, R780, B684)
   | [u'Location:Static', u'Location:', u'Static2']
   | child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13089", control_type="Text")
   |
   | Edit - 'Location:'    (L790, T669, R1036, B692)
   | ['Edit3', u'Location:Edit', u'5']
   | child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13065", control_type="Edit")
   |
   | Static - 'Size:'    (L717, T695, R780, B710)
   | [u'Size:Static', u'Size:', u'Static3']
   | child_window(data-gt-translate-attributes='["title"]' title="Size:", auto_id="13081", control_type="Text")
   |
   | Edit - 'Size:'    (L790, T695, R1036, B718)
   | ['Edit4', u'6', u'Size:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Size:", auto_id="13064", control_type="Edit")
   |
   | Static - 'Size on disk:'    (L717, T721, R780, B736)
   | [u'Size on disk:', u'Size on disk:Static', u'Static4']
   | child_window(data-gt-translate-attributes='["title"]' title="Size on disk:", auto_id="13107", control_type="Text")
   |
   | Edit - 'Size on disk:'    (L790, T721, R1036, B744)
   | ['Edit5', u'7', u'Size on disk:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Size on disk:", auto_id="13106", control_type="Edit")
   |
   | Static - 'Contains:'    (L717, T747, R780, B762)
   | [u'Contains:1', u'Contains:0', u'Contains:Static', u'Static5', u'Contains:']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13088", control_type="Text")
   |
   | Edit - 'Contains:'    (L790, T747, R1036, B770)
   | [u'8', 'Edit6', u'Contains:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")
   |
   | Image - 'Contains:'    (L717, T773, R1035, B775)
   | [u'Contains:Image', 'Image3', u'Contains:2']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13096", control_type="Image")
   |
   | Static - 'Created:'    (L717, T786, R780, B801)
   | [u'Created:', u'Created:Static', u'Static6', u'Created:1', u'Created:0']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13092", control_type="Text")
   |
   | Edit - 'Created:'    (L790, T786, R1036, B809)
   | [u'Created:Edit', 'Edit7', u'9']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13072", control_type="Edit")
   |
   | Image - 'Created:'    (L717, T812, R1035, B814)
   | [u'Created:Image', 'Image4', u'Created:2']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13097", control_type="Image")
   |
   | Static - 'Attributes:'    (L717, T825, R780, B840)
   | [u'Attributes:Static', u'Static7', u'Attributes:']
   | child_window(data-gt-translate-attributes='["title"]' title="Attributes:", auto_id="13091", control_type="Text")
   |
   | CheckBox - 'Read-only (Only applies to files in folder)'    (L790, T825, R1035, B841)
   | [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Read-only (Only applies to files in folder)CheckBox', u'Read-only (Only applies to files in folder)']
   | child_window(data-gt-translate-attributes='["title"]' title="Read-only (Only applies to files in folder)", auto_id="13075", control_type="CheckBox")
   |
   | CheckBox - 'Hidden'    (L790, T848, R865, B864)
   | ['CheckBox2', u'HiddenCheckBox', u'Hidden']
   | child_window(data-gt-translate-attributes='["title"]' title="Hidden", auto_id="13076", control_type="CheckBox")
   |
   | Button - 'Advanced...'    (L930, T845, R1035, B868)
   | [u'Advanced...', u'Advanced...Button', 'Button', u'Button1', u'Button0']
   | child_window(data-gt-translate-attributes='["title"]' title="Advanced...", auto_id="13154", control_type="Button")
   |
   | Button - 'OK'    (L814, T968, R889, B991)
   | ['Button2', u'OK', u'OKButton']
   | child_window(data-gt-translate-attributes='["title"]' title="OK", auto_id="1", control_type="Button")
   |
   | Button - 'Cancel'    (L895, T968, R970, B991)
   | ['Button3', u'CancelButton', u'Cancel']
   | child_window(data-gt-translate-attributes='["title"]' title="Cancel", auto_id="2", control_type="Button")
   |
   | Button - 'Apply'    (L976, T968, R1051, B991)
   | ['Button4', u'ApplyButton', u'Apply']
   | child_window(data-gt-translate-attributes='["title"]' title="Apply", auto_id="12321", control_type="Button")
   |
   | TabControl - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabControlSharing', u'TabControlPrevious Versions', u'TabControlSecurity', u'TabControl', u'TabControlCustomize']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'General'    (L704, T558, R753, B576)
   |    | [u'GeneralTabItem', 'TabItem', u'General', u'TabItem0', u'TabItem1']
   |    | child_window(data-gt-translate-attributes='["title"]' title="General", control_type="TabItem")
   |    |
   |    | TabItem - 'Sharing'    (L753, T558, R801, B576)
   |    | [u'Sharing', u'SharingTabItem', 'TabItem2']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Sharing", control_type="TabItem")
   |    |
   |    | TabItem - 'Security'    (L801, T558, R851, B576)
   |    | [u'Security', 'TabItem3', u'SecurityTabItem']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Security", control_type="TabItem")
   |    |
   |    | TabItem - 'Previous Versions'    (L851, T558, R947, B576)
   |    | [u'Previous VersionsTabItem', u'Previous Versions', 'TabItem4']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Previous Versions", control_type="TabItem")
   |    |
   |    | TabItem - 'Customize'    (L947, T558, R1007, B576)
   |    | [u'CustomizeTabItem', 'TabItem5', u'Customize']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Customize", control_type="TabItem")
   |
   | TitleBar - 'None'    (L712, T521, R1057, B549)
   | ['TitleBar', u'11']
   |    |
   |    | Menu - 'System'    (L696, T526, R718, B548)
   |    | [u'System0', u'System', u'System1', u'Menu', u'SystemMenu']
   |    | child_window(data-gt-translate-attributes='["title"]' title="System", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | MenuItem - 'System'    (L696, T526, R718, B548)
   |    |    | [u'System2', u'MenuItem', u'SystemMenuItem']
   |    |    | child_window(data-gt-translate-attributes='["title"]' title="System", control_type="MenuItem")
   |    |
   |    | Button - 'Close'    (L1024, T519, R1058, B549)
   |    | [u'CloseButton', u'Close', 'Button5']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Close", control_type="Button")

W niektórych przypadkach wydruk całego drzewa może być powolny (np. w iTunes na jednej zakładce znajdują się aż trzy tysiące elementów!), ale można skorzystać z opcji depth (głębokość): depth=1 - sam element, depth=2 - tylko bezpośrednie dzieci i tak dalej. Można to również określić w specyfikacjach podczas tworzenia child_window.

Примеры

Stale uzupełniamy lista przykładów w repozytorium. Wśród najnowszych warto zwrócić uwagę na automatyzację analizatora sieci WireShark (jest to dobry przykład aplikacji Qt5; choć zadanie to można rozwiązać bez GUI, ponieważ istnieje scapy.Sniffer z pakietu Pythona przerażający). Poniżej znajduje się również przykład automatyzacji programu MS Paint z paskiem narzędzi Wstążka.

Kolejny świetny przykład napisany przez mojego ucznia: przeciągając plik z explorer.exe na stronę Chrome dla Dysku Google (nieco później przeniesie się do głównego repozytorium).

I oczywiście przykład subskrypcji zdarzeń klawiatury (skrótów klawiszowych) i myszy:
hook_and_listen.py.

Podziękowanie

Szczególne podziękowania należą się tym, którzy stale pomagają w rozwoju projektu. Dla mnie i Dzień Walentyna To jest trwałe hobby. Dwóch moich studentów z UNN obroniło niedawno stopnie licencjackie na ten temat. Alexander wniósł duży wkład w obsługę MS UI Automation i niedawno zaczął tworzyć automatyczny generator kodu oparty na zasadzie „odtwarzania nagrania” w oparciu o właściwości tekstowe (jest to najbardziej złożona funkcja), jak dotąd tylko dla zaplecza „uia”. Ivan opracowuje nowe zaplecze dla Linux na podstawie AT-SPI (moduły mouse и keyboard na podstawie python-xlib - już w wersjach 0.6.x).

Ponieważ od dłuższego czasu prowadzę specjalny kurs dotyczący automatyzacji w Pythonie, niektórzy studenci studiów magisterskich odrabiają prace domowe, wdrażając drobne funkcje lub przykłady automatyzacji. Studenci odkryli kiedyś pewne kluczowe rzeczy na etapie badań. Chociaż czasami trzeba ściśle monitorować jakość kodu. Bardzo pomagają w tym analizatory statyczne (QuantifiedCode, Codacy i Landscape) oraz automatyczne testy w chmurze (usługa AppVeyor) z pokryciem kodu na poziomie około 95%.

Dziękuję także wszystkim, którzy zostawiają recenzje, zgłaszają błędy i wysyłają prośby o ściągnięcie!

Dodatkowe zasoby

Podążamy za pytaniami tag na StackOverflow (ostatnio pojawił się tag w rosyjskiej wersji SO) I według słowa kluczowego na Tosterze. Jest Czat w języku rosyjskim na Gitterze.

Aktualizujemy co miesiąc ocena bibliotek open source do testowania GUI. Pod względem liczby gwiazdek na GitHubie szybciej rosną jedynie Autohotkey (mają bardzo dużą społeczność i długą historię) oraz PyAutoGUI (głównie za sprawą popularności książek jej autora Ala Sweigarta: „Automate the Boring Stuff with Python” i inne).

Źródło: www.habr.com

Kup niezawodny hosting dla stron z ochroną DDoS, serwery VPS VDS 🔥 Kup niezawodny hosting stron internetowych z ochroną DDoS, serwery VPS VDS | ProHoster