Automatizăm GUI desktop pe Python + pywinauto: cum să ne împăcăm cu MS UI Automation

Biblioteca Python pywinauto — este un proiect open source pentru automatizarea aplicațiilor GUI pe desktop în Windows. În ultimii doi ani, au apărut caracteristici majore noi:

  • Suport pentru tehnologia MS UI Automation. Interfața rămâne neschimbată, și acum sunt acceptate: WinForms, WPF, Qt5, Windows Store (UWP) și altele — aproape tot ce există pe Windows.
  • Sistem de backend-uri/pluginuri (acum sunt două sub capotă: cel implicit "win32" și noul "uia"). Avansăm treptat spre suportul multiplatformă.
  • Hook-uri Win32 pentru mouse și tastatură (taste rapide în stilul pyHook).

De asemenea, vom face o mică revizuire a ceea ce există în open source pentru automatizarea desktopului (fără pretenții de comparație serioasă).

Acest articol este o transcriere parțială a unei prezentări de la conferința SQA Days 20 din Minsk (înregistrarea video și slide-uri), parțial versiunea în limba română Ghidul de Încercare pentru pywinauto.

Să începem cu o scurtă revizuire a open source în acest domeniu. Pentru aplicațiile GUI desktop, lucrurile sunt ceva mai complicate decât pentru web, care are Selenium. Iată principalele abordări:

Metoda coordonatelor

Hardcodăm punctele de clic, sperăm la întâmplări fericite.
[+] Multiplatformă, ușor de implementat.
[+] Ușor de realizat o înregistrare "record-replay" a testelor.
[-] Cel mai instabil la schimbări ale rezoluției ecranului, temelor, fonturilor, dimensiunilor feroneriilor etc.
[-] Necesită eforturi uriașe pentru suport, adesea este mai simplu să regenerăm testele de la zero sau să testăm manual.
[-] Automatizează doar acțiunile, pentru verificare și extragerea datelor există alte metode.

Instrumente (multiplatformă): autopy, PyAutoGUI, PyUserInput și multe altele. De obicei, instrumentele mai complexe includ această funcționalitate (nu întotdeauna multiplatformă).

Merită menționat că metoda coordonatelor poate completa celelalte abordări. De exemplu, pentru grafică personalizată, se pot face clicuri pe coordonate relative (de la colțul stânga sus al feroneriilor/elementelor, nu al întregului ecran) — de obicei, aceasta este destul de sigură, mai ales dacă ținem cont de lungimea/ lățimea întregului element (astfel, o rezoluție ecran diferită nu va afecta).

O altă opțiune: a aloca pentru teste doar o mașină cu configurații stabile (nu este cross-platform, dar în anumite cazuri este adecvat).

Recunoașterea imaginilor de referință

[+] Cross-platform
[+-] Relativ fiabil (mai bun decât metoda coordonatelor), dar totuși necesită trucuri.
[-+] Relativ lent, deoarece necesită resurse CPU pentru algoritmii de recunoaștere.
[-] Despre recunoașterea textului (OCR), de obicei, nu se discută => nu se pot obține date textuale. Din câte știu, soluțiile OCR existente nu sunt prea fiabile pentru acest tip de sarcini și nu au o utilizare largă (aștept comentarii, dacă nu mai este așa).

Instrumente: Sikuli, Lackey (compatible cu Sikuli, scris complet în Python), PyAutoGUI.

Tehnologii de accesibilitate

[+] Cea mai fiabilă metodă, deoarece permite căutarea după text, indiferent de modul în care este redat de sistem sau cadru.
[+] Permite extragerea datelor textuale => mai ușor de verificat rezultatele testelor.
[+] De obicei, cea mai rapidă, deoarece consumă aproape zero resurse CPU.
[-] Dificil de realizat un instrument cross-platform: absolut toate bibliotecile open-source suportă una sau două tehnici de accesibilitate. Windows/Linux/MacOS nu sunt complet suportate de nimeni, cu excepția celor plătite precum TestComplete, UFT sau Squish.
[-] Nu întotdeauna o astfel de tehnologie este practic disponibilă. De exemplu, testarea ecranului de boot în interiorul VirtualBox-ului — aici nu se poate evita recunoașterea imaginilor. Dar în multe cazuri clasice, totuși, abordarea de accesibilitate este aplicabilă. Despre aceasta va fi vorba mai departe.

Instrumente: TestStack.White în C#, Winium.Desktop în C# (compatibil cu Selenium), MS WinAppDriver în C# (compatibil cu Appium), pywinauto, pyatom (compatibil cu LDTP), Python-UIAutomation-for-Windows, RAutomation în Ruby, LDTP (Linux Desktop Testing Project) și versiunea sa pentru Windows Cobra.

LDTP — probabil singurul instrument open-source cross-platform (mai exact, un grup de biblioteci) bazat pe tehnologiile de accesibilitate. Totuși, nu este foarte popular. Nu l-am folosit personal, dar din recenzii reiese că interfața sa nu este cea mai prietenoasă. Dacă există recenzii pozitive, vă rog să le împărtășiți în comentarii.

Backdoor de testare (a.k.a. bicicletă internă)

Pentru aplicațiile cross-platform, dezvoltatorii adesea creează un mecanism intern pentru a asigura testabilitatea. De exemplu, creează un server TCP de serviciu în aplicație, la care testele se conectează și trimit comenzi textuale: pe ce să apese, de unde să ia datele etc. Fiabil, dar nu universal.

Principalele tehnologii de accesibilitate desktop

Vechea bună interfață Win32 API

Cele mai multe aplicații Windows, scrise înainte de lansarea WPF și ulterior a Windows Store, sunt construite într-un fel sau altul pe API-ul Win32. Anume, MFC, WTL, C++ Builder, Delphi, VB6 — toate aceste unelte utilizează API-ul Win32. Chiar și Windows Forms — într-o măsură considerabilă, compatibil cu API-ul Win32.

Instrumente: AutoIt (asemănător cu VB) și un wrapper Python pyautoit, AutoHotkey (limbaj propriu, există un interfață IDispatch COM), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).

Microsoft UI Automation

Principalul avantaj: tehnologia MS UI Automation suportă majoritatea aplicațiilor GUI pe Windows, cu rare excepții. Problema: nu este mult mai ușor de învățat decât API-ul Win32. Altfel, nimeni nu ar crea wrapper-e pentru ea.

De fapt, acesta este un set de interfețe COM personalizate (în principal, UIAutomationCore.dll), și de asemenea are o suprafață .NET sub forma namespace System.Windows.Automation. Apropo, are un bug introdus care poate face ca unele elemente UI să fie omise. Prin urmare, este mai bine să folosiți direct UIAutomationCore.dll (dacă ați auzit despre UiaComWrapper pe C#, asta este).

Variante ale interfețelor COM:

(1) IUknown de bază — „rădăcina tuturor relelor”. Cel mai de jos nivel, deloc prietenos cu utilizatorul.
(2) IDispatch și derivate (de exemplu, Excel.Application), care pot fi folosite în Python cu ajutorul pachetului win32com.client (inclus în pyWin32). Cea mai convenabilă și frumoasă opțiune.
(3) Interfețe personalizate cu care lucrează pachetul Python terț comtypes.

Instrumente: TestStack.White în C#, pywinauto 0.6.0+, Winium.Desktop în C#, Python-UIAutomation-for-Windows (ei au codul sursă al wrapper-elor C pentru UIAutomationCore.dll netransparent), RAutomation în Ruby.

AT-SPI

Deși aproape toate distribuțiile din familia Linux sunt construite pe X Window System (în Fedora 25 „x-urile” au fost înlocuite cu Wayland), „x-urile” permit operarea doar cu feronierul de nivel superior și cu mouse-ul/tastatura. Pentru o analiză detaliată a butoanelor, listelor și așa mai departe — există tehnologia AT-SPI. Cele mai populare gestiuni de feronier au așa-numitul demon AT-SPI registry, care oferă un GUI automatizabil pentru aplicații (cel puțin suportă Qt și GTK).

Instrumente: pyatspi2.

pyatspi2, în opinia mea, conține prea multe dependențe, precum PyGObject. Tehnologia în sine este disponibilă sub formă de bibliotecă dinamică obișnuită libatspi.so. Acesta poate fi accesat Manual de Referință. Pentru biblioteca pywinauto, ne propunem să realizăm suport AT-SPI exact așa: prin încărcarea libatspi.so și modulul ctypes. Există o mică problemă doar în utilizarea versiunii corecte, deoarece pentru aplicațiile GTK+ și Qt sunt ușor diferite. O lansare probabilă a pywinauto 0.7.0 cu suport complet pentru Linux se poate aștepta în prima jumătate a anului 2018.

Apple Accessibility API

Pe MacOS, există un limbaj de automatizare propriu, AppleScript. Pentru a implementa ceva similar în Python, desigur, trebuie să folosești funcții din ObjectiveC. Începând, se pare, cu MacOS 10.6, python-ul preinstalat include pachetul pyobjc. Acesta va ușura de asemenea lista de dependențe pentru suportul viitor în pywinauto.

Instrumente: Pe lângă limbajul Apple Script, merită să acorzi atenție la ATOMac, cunoscut și sub numele de pyatom. Acesta este compatibil în ceea ce privește interfața cu LDTP, dar este de asemenea o bibliotecă autonomă. Pe el există un exemplu de automatizare a iTunes pe macOs, scris de studentul meu. Există o problemă cunoscută: temporizările flexibile nu funcționează (metodele waitFor*). Dar, în general, este o opțiune destul de bună.

Cum să începi să lucrezi cu pywinauto

Primul pas este să te echipezi cu un inspector de obiecte GUI (cum îi spun unii, Spy tool). Acesta te va ajuta să studiezi aplicația din interior: cum este structurată ierarhia elementelor, ce proprietăți sunt disponibile. Cele mai cunoscute inspectoare de obiecte sunt:

  • Spy++ — inclus în pachetul Visual Studio, inclusiv Express sau Community Edition. Folosește Win32 API. De asemenea, este cunoscut clonul său AutoIt Window Info.
  • Inspect.exe — inclus în Windows SDK. Dacă este instalat, pe Windows de 64 de biți îl poți găsi în folderul C:Program Files (x86)Windows Kitsbinx64. În interiorul inspectorului, trebuie să selectezi modul UI Automation în loc de MS AA (Active Accessibility, precursorul UI Automation).

După ce ai analizat aplicația în detaliu, alegi backend-ul pe care îl vei folosi. Este suficient să specifici numele backend-ului atunci când creezi obiectul Application.

  • backend="win32" — este utilizat în prezent ca implicit, funcționează bine cu MFC, WTL, VB6 și alte aplicații legacy.
  • backend="uia" — un nou backend pentru MS UI Automation: funcționează perfect cu WPF și WinForms; de asemenea, este bun pentru aplicațiile Delphi și Windows Store; funcționează cu Qt5 și unele aplicații Java. În general, dacă Inspect.exe vede elementele și proprietățile lor, înseamnă că acest backend este potrivit. Practic, majoritatea browserelor suportă de asemenea UI Automation (Mozilla în mod implicit, iar Chrome, la lansare, trebuie să primească cheia comenzii de linie --force-renderer-accessibility, pentru a vedea elementele de pe pagini în Inspect.exe). Desigur, competiția cu Selenium în acest domeniu este improbabilă. Este doar un alt mod de a lucra cu browserul (poate fi util pentru scenarii inter-produs).

Punctele de intrare pentru automatizare

Aplicația este bine studiată. E timpul să creezi un obiect Application și să-l pornești sau să te alături unuia deja pornit. Acesta nu este doar un clon al clasei standard subprocess.Popen, și anume un obiect introdus care restricționează toate acțiunile dvs. la limitele procesului. Acest lucru este foarte util dacă sunt pornite mai multe instanțe ale aplicației și nu doriți să interveniți asupra celorlalte.

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

# Vom descrie fereastra pe care dorim să o găsim în procesul Notepad.exe
dlg_spec = app.UntitledNotepad
# așteptăm ca fereastra să apară cu adevărat
actionable_dlg = dlg_spec.wait('visible')

Dacă doriți să controlați mai multe aplicații simultan, clasa Desktopvă va ajuta. De exemplu, în calculatorul de pe Win10, ierarhia elementelor este dispersată pe mai multe procese (nu doar calc.exe). Așa că fără obiectul Desktop nu se poate.

from subprocess import Popen
from pywinauto import Desktop

Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')

Obiectul rădăcină (Application sau Desktop) este singurul loc în care trebuie să specifici backendul. Tot restul se integrează transparent în conceptul „specificație->wrapper”, despre care vom vorbi mai departe.

Specificațiile feroneriilor/elementelor

Aceasta este concepția de bază pe care se bazează interfața pywinauto. Puteți descrie fereastra/elementul aproximativ sau mai detaliat, chiar dacă acesta nu există încă sau a fost deja închis. Specificația feronului (obiectul WindowSpecification) conține criteriile pe care trebuie să le urmați pentru a căuta fereastra sau elementul real.

Un exemplu de specificație detaliată a feronului:

>>> dlg_spec = app.window(title='Untitled - Notepad')

>>> dlg_spec


>>> dlg_spec.wrapper_object()

Căutarea feronului se face prin apelarea metodei .wrapper_object(). Aceasta returnează un fel de „wrapper” pentru fereastra/elementul real sau aruncă ElementNotFoundError (uneori ElementAmbiguousError, dacă au fost găsite mai multe elemente, adică este necesară clarificarea criteriului de căutare). Acest „wrapper” știe deja să efectueze unele acțiuni cu elementul sau să obțină date din el.

Python poate ascunde apelul .wrapper_object(), astfel încât codul final devine mai scurt. Recomandăm utilizarea acestuia doar pentru debugging. Următoarele două linii fac exact același lucru:

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

Există multe criterii de căutare pentru specificația feronului. Iată doar câteva exemple:

# могут иметь несколько уровней
app.window(title_re='.* - Notepad$').window(class_name='Edit')

# можно комбинировать критерии (как AND) и не ограничиваться одним процессом приложения
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')

Lista tuturor criteriilor posibile este în documentația funcției pywinauto.findwindows.find_elements(…).

Magia accesului pe bază de atribut și cheie

Python simplifică crearea specificațiilor feronului și recunoaște atributele obiectului dinamic (în interior metoda este suprascrisă __getattribute__). Desigur, numele atributului are aceleași restricții ca și numele oricărei variabile (nu poți insera spații, virgule sau alte simboluri speciale). Din fericire, pywinauto folosește un algoritm denumit „best match” pentru căutare, care este rezistent la greșeli de tipar și variații mici.

app.UntitledNotepad
# același lucru cu
app.window(best_match='UntitledNotepad')

Dacă ai nevoie totuși de șiruri Unicode (de exemplu, pentru limba rusă), spațiile etc., poți accesa prin cheie (de parcă ar fi un dicționar obișnuit):

app['Untitled - Notepad']
# același lucru cu
app.window(best_match='Untitled - Notepad')

Cinci reguli pentru nume magice

Cum poți afla numele magice de referință? Cele care sunt atribuite elementului înainte de căutare. Dacă ai indicat un nume destul de asemănător cu referința, elementul va fi găsit.

  1. După titlu (text, nume): app.Properties.OK.click()
  2. Dupa text și tipul de element: app.Properties.OKButton.click()
  3. După tip și număr: app.Properties.Button3.click() (nume Button0 și Button1 sunt legate de primul element găsit, Button2 — de al doilea, și mai departe în ordinea istorică)
  4. După textul static (din stânga sau de sus) și după tip: app.OpenDialog.FileNameEdit.set_text("") (util pentru elemente cu text dinamic)
  5. După tip și textul din interior: app.Properties.TabControlSharing.select("General")

De obicei, două-trei reguli sunt aplicate simultan,rareori mai multe. Pentru a verifica ce nume sunt disponibile pentru fiecare element, poți folosi metoda print_control_identifiers(). Aceasta poate imprima arborele elementelor atât pe ecran, cât și într-un fișier. Pentru fiecare element sunt imprimate numele magice de referință. De asemenea, poți copia de acolo specificații mai detaliate pentru elementele copil. Rezultatul în script va arăta astfel:

app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")

Întregul arbore de elemente — de obicei, un document destul de mare.

>>> app.Properties.print_control_identifiers()

Identificatori de Control:

Dialog - 'Proprietăți Windows NT'    (L688, T518, R1065, B1006)
[u'Windows NT PropertiesDialog', u'Dialog', u'Windows NT Properties']
child_window(title="Proprietăți Windows NT", control_type="Window")
   |
   | Imagine - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Imagine1', u'Imagine0', 'Imagine', u'1']
   | child_window(auto_id="13057", control_type="Imagine")
   |
   | Imagine - ''    (L717, T630, R1035, B632)
   | ['Imagine2', u'2']
   | child_window(auto_id="13095", control_type="Imagine")
   |
   | Editare - 'Numele folderului:'    (L790, T596, R1036, B619)
   | [u'3', 'Editare', u'Editare1', u'Editare0']
   | child_window(title="Numele folderului:", auto_id="13156", control_type="Edit")
   |
   | Static - 'Tip:'    (L717, T643, R780, B658)
   | [u'Tip:Static', u'Static', u'Static1', u'Static0', u'Tip:']
   | child_window(title="Tip:", auto_id="13080", control_type="Text")
   |
   | Editare - 'Tip:'    (L790, T643, R1036, B666)
   | [u'4', 'Editare2', u'Tip:Edit']
   | child_window(title="Tip:", auto_id="13059", control_type="Edit")
   |
   | Static - 'Locație:'    (L717, T669, R780, B684)
   | [u'Locație:Static', u'Locație:', u'Static2']
   | child_window(title="Locație:", auto_id="13089", control_type="Text")
   |
   | Editare - 'Locație:'    (L790, T669, R1036, B692)
   | ['Editare3', u'Locație:Edit', u'5']
   | child_window(title="Locație:", auto_id="13065", control_type="Edit")
   |
   | Static - 'Dimensiune:'    (L717, T695, R780, B710)
   | [u'Dimensiune:Static', u'Dimensiune:', u'Static3']
   | child_window(title="Dimensiune:", auto_id="13081", control_type="Text")
   |
   | Editare - 'Dimensiune:'    (L790, T695, R1036, B718)
   | ['Editare4', u'6', u'Dimensiune:Edit']
   | child_window(title="Dimensiune:", auto_id="13064", control_type="Edit")
   |
   | Static - 'Dimensiune pe disc:'    (L717, T721, R780, B736)
   | [u'Dimensiune pe disc:', u'Dimensiune pe disc:Static', u'Static4']
   | child_window(title="Dimensiune pe disc:", auto_id="13107", control_type="Text")
   |
   | Editare - 'Dimensiune pe disc:'    (L790, T721, R1036, B744)
   | ['Editare5', u'7', u'Dimensiune pe disc:Edit']
   | child_window(title="Dimensiune pe disc:", auto_id="13106", control_type="Edit")
   |
   | Static - 'Conține:'    (L717, T747, R780, B762)
   | [u'Conține:1', u'Conține:0', u'Conține:Static', u'Static5', u'Conține:']
   | child_window(title="Conține:", auto_id="13088", control_type="Text")
   |
   | Editare - 'Conține:'    (L790, T747, R1036, B770)
   | [u'8', 'Editare6', u'Conține:Edit']
   | child_window(title="Conține:", auto_id="13087", control_type="Edit")
   |
   | Imagine - 'Conține:'    (L717, T773, R1035, B775)
   | [u'Conține:Imagine', 'Imagine3', u'Conține:2']
   | child_window(title="Conține:", auto_id="13096", control_type="Imagine")
   |
   | Static - 'Creat:'    (L717, T786, R780, B801)
   | [u'Creat:', u'Creat:Static', u'Static6', u'Creat:1', u'Creat:0']
   | child_window(title="Creat:", auto_id="13092", control_type="Text")
   |
   | Editare - 'Creat:'    (L790, T786, R1036, B809)
   | [u'Creat:Edit', 'Editare7', u'9']
   | child_window(title="Creat:", auto_id="13072", control_type="Edit")
   |
   | Imagine - 'Creat:'    (L717, T812, R1035, B814)
   | [u'Creat:Imagine', 'Imagine4', u'Creat:2']
   | child_window(title="Creat:", auto_id="13097", control_type="Imagine")
   |
   | Static - 'Atribute:'    (L717, T825, R780, B840)
   | [u'Atribute:Static', u'Static7', u'Atribute:']
   | child_window(title="Atribute:", auto_id="13091", control_type="Text")
   |
   | CheckBox - 'Doar citire (Se aplică doar fișierelor din folder)'    (L790, T825, R1035, B841)
   | [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Doar citire (Se aplică doar fișierelor din folder)CheckBox', u'Doar citire (Se aplică doar fișierelor din folder)']
   | child_window(title="Doar citire (Se aplică doar fișierelor din folder)", auto_id="13075", control_type="CheckBox")
   |
   | CheckBox - 'Ascuns'    (L790, T848, R865, B864)
   | ['CheckBox2', u'CheckBoxAscuns', u'Ascuns']
   | child_window(title="Ascuns", auto_id="13076", control_type="CheckBox")
   |
   | Buton - 'Avansat...'    (L930, T845, R1035, B868)
   | [u'Avansat...', u'ButonAvansat...', 'Buton', u'Buton1', u'Buton0']
   | child_window(title="Avansat...", auto_id="13154", control_type="Buton")
   |
   | Buton - 'OK'    (L814, T968, R889, B991)
   | ['Buton2', u'OK', u'ButonOK']
   | child_window(title="OK", auto_id="1", control_type="Buton")
   |
   | Buton - 'Anulează'    (L895, T968, R970, B991)
   | ['Buton3', u'ButonAnulează', u'Anulează']
   | child_window(title="Anulează", auto_id="2", control_type="Buton")
   |
   | Buton - 'Aplică'    (L976, T968, R1051, B991)
   | ['Buton4', u'ButonAplică', u'Aplică']
   | child_window(title="Aplică", auto_id="12321", control_type="Buton")
   |
   | ControlTab - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabControlPartajare', u'TabControlVersiuni Anterioare', u'TabControlSecuritate', u'TabControl', u'TabControlPersonalizează']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'General'    (L704, T558, R753, B576)
   |    | [u'TabItemGeneral', 'TabItem', u'General', u'TabItem0', u'TabItem1']
   |    | child_window(title="General", control_type="TabItem")
   |    |
   |    | TabItem - 'Partajare'    (L753, T558, R801, B576)
   |    | [u'Partajare', u'TabItemPartajare', 'TabItem2']
   |    | child_window(title="Partajare", control_type="TabItem")
   |    |
   |    | TabItem - 'Securitate'    (L801, T558, R851, B576)
   |    | [u'Securitate', 'TabItem3', u'TabItemSecuritate']
   |    | child_window(title="Securitate", control_type="TabItem")
   |    |
   |    | TabItem - 'Versiuni anterioare'    (L851, T558, R947, B576)
   |    | [u'TabItemVersiuniAnterioare', u'Versiuni anterioare', 'TabItem4']
   |    | child_window(title="Versiuni anterioare", control_type="TabItem")
   |    |
   |    | TabItem - 'Personalizează'    (L947, T558, R1007, B576)
   |    | [u'TabItemPersonalizează', 'TabItem5', u'Personalizează']
   |    | child_window(title="Personalizează", control_type="TabItem")
   |
   | BaraTitlu - 'Nimic'    (L712, T521, R1057, B549)
   | ['BaraTitlu', u'11']
   |    |
   |    | Meniu - 'Sistem'    (L696, T526, R718, B548)
   |    | [u'Sistem0', u'Sistem', u'Sistem1', u'Meniu', u'MeniuSistem']
   |    | child_window(title="Sistem", auto_id="MeniuBar", control_type="MeniuBar")
   |    |    |
   |    |    | ElementMeniu - 'Sistem'    (L696, T526, R718, B548)
   |    |    | [u'Sistem2', u'ElementMeniu', u'ElementMeniuSistem']
   |    |    | child_window(title="Sistem", control_type="ElementMeniu")
   |    |
   |    | Buton - 'Închide'    (L1024, T519, R1058, B549)
   |    | [u'ButonÎnchide', u'Închide', 'Buton5']
   |    | child_window(title="Închide", control_type="Buton")

În unele cazuri, imprimarea întregului arbore poate fi lentă (de exemplu, în iTunes, pe un singur tab sunt chiar trei mii de elemente!), dar se poate folosi parametrul depth (adâncime): depth=1 — elementul însuși, depth=2 — doar copiii direcți, și așa mai departe. Acesta poate fi specificat și în specificațiile la crearea child_window.

Exemple

Împărtășim constant lista de exemple din depozit. Dintre cele recente, se remarcă automatizarea analizoarelor de rețea WireShark (un exemplu bun de aplicație Qt5; deși această sarcină poate fi îndeplinită și fără GUI, deoarece există scapy.Sniffer din pachetul Python scapy). De asemenea, există un exemplu de automatizare a MS Paint cu bara sa de instrumente Ribbon.

Un alt exemplu excelent, scris de studentul meu: traseul unui fișier din explorer.exe pe o pagină Chrome pentru Google Drive (va fi mutat în depozitul principal puțin mai târziu).

Și, desigur, un exemplu de abonare la evenimentele tastaturii (taste rapide) și mouse-ului:
hook_and_listen.py.

Mulțumiri

Un mulțumesc special — celor care ne ajută constant să dezvoltăm proiectul. Pentru mine și Valentina este o pasiune constantă. Doi dintre studenții mei de la NNGU și-au apărat recent diplomile de bachelor pe această temă. Alexandru a contribuit semnificativ la suportul MS UI Automation și a început recent să dezvolte un generator automat de cod pe baza principiului „înregistrare-redare” bazat pe proprietăți textuale (aceasta este cea mai complexă caracteristică), deocamdată doar pentru backend-ul „uia”. Ivan dezvoltă un nou backend pentru Linux bazat pe AT-SPI (module mouse și tastatură bazat pe python-xlib — deja în releasurile 0.6.x).

Deoarece predau de ceva vreme un curs special de automatizare în Python, parte din studenții de master realizează teme implementând mici funcții sau exemple de automatizare. Unele lucruri cheie aflate în faza de cercetare au fost descoperite tot de studenți. Cu toate acestea, uneori, este necesar să monitorizăm strict calitatea codului. Analizatoarele statice (QuantifiedCode, Codacy și Landscape) și testele automate în cloud (serviciul AppVeyor) cu o acoperire a codului de aproximativ 95% ajută foarte mult în acest sens.

De asemenea, mulțumiri tuturor celor care lasă feedback, deschid bug-uri și trimit cereri de pull!

Resurse suplimentare

Monitorizăm întrebările prin tagul de pe StackOverflow (a apărut recent tag pe versiunea în limba română SO) și după cuvântul cheie pe Toster. Există chat-ul în limba română în Gitter.

Actualizăm fiecare lună clasamentul bibliotecilor open-source pentru testarea GUI. Numărul de stele de pe GitHub este depășit doar de Autohotkey (care are o comunitate foarte mare și o istorie lungă) și PyAutoGUI (în mare parte datorită popularității cărților autorului său Al Sweigart: „Automatizați sarcinile plictisitoare cu Python” și altele).

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster