Automatiseren van desktop GUI met Python + pywinauto: hoe je kunt samenwerken met MS UI Automation

Python bibliotheek pywinauto — is een open-source project voor het automatiseren van desktop GUI-applicaties op Windows. In de afgelopen twee jaar zijn er nieuwe grote functies toegevoegd:

  • Ondersteuning voor de MS UI Automation-technologie. De interface is hetzelfde, maar nu worden ondersteund: WinForms, WPF, Qt5, Windows Store (UWP), en meer — bijna alles wat op Windows aanwezig is.
  • Een systeem van backends/plugins (er zijn er momenteel twee onder de motorkap: de standaard "win32" en de nieuwe "uia"). We bewegen langzaam in de richting van cross-platform functionaliteit.
  • Win32 hooks voor muis en toetsenbord (hotkeys in de geest van pyHook).

We zullen ook een kort overzicht geven van wat er in open-source beschikbaar is voor desktopautomatisering (zonder de claim op serieuze vergelijking).

Dit artikel is gedeeltelijk een transcriptie van een lezing op de SQA Days 20-conferentie in Minsk (videorecording en dia's), gedeeltelijk de Russische versie Getting Started Guide voor pywinauto.

Laten we beginnen met een kort overzicht van open-source in deze sector. Voor desktop GUI-applicaties is het iets ingewikkelder dan voor het web, dat Selenium heeft. Dit zijn de belangrijkste benaderingen:

Coördinatenmethode

Hardcoded klikpunten, hopen op succesvolle treffen.
[+] Cross-platform, gemakkelijk te implementeren.
[+] Makkelijk een 'record-replay' opnames van tests te maken.
[-] Het meest onstabiel bij het veranderen van schermresolutie, thema's, lettertypen, venstergroottes, enz.
[-] Vereist enorme inspanningen voor onderhoud, vaak is het eenvoudiger om tests opnieuw te genereren of handmatig te testen.
[-] Automatiseert alleen acties, voor verificatie en gegevensextractie zijn andere methodes nodig.

Tools (cross-platform): autopy, PyAutoGUI, PyUserInput en vele anderen. Over het algemeen bevatten complexere tools deze functionaliteit (niet altijd cross-platform).

De coördinatiemethode kan andere benaderingen aanvullen. Voor aangepaste graphics kun je bijvoorbeeld klikken op relatieve coördinaten (vanaf de linkerbovenhoek van het venster/element, niet het hele scherm) - dit is meestal behoorlijk betrouwbaar, vooral als je rekening houdt met de lengte/breedte van het gehele element (dan maakt een verschillende schermresolutie niet uit).

Een andere optie: alleen één machine met stabiele instellingen selecteren voor testen (niet cross-platform, maar in sommige gevallen bruikbaar).

Herkenning van referentieafbeeldingen

[+] Cross-platform
[+-] Relatief betrouwbaar (beter dan de coördinatiemethode), maar vereist nog steeds enige slimheid.
[-+] Relatief traag, omdat het CPU-kracht vereist voor de herkenningsalgoritmen.
[-] Over tekstherkenning (OCR) wordt meestal niet gesproken => tekstuele gegevens kunnen niet worden verkregen. Voor zover ik weet, zijn de huidige OCR-oplossingen niet erg betrouwbaar voor dit soort taken en hebben ze geen brede toepassing (reacties zijn welkom als dat inmiddels niet meer het geval is).

Hulpmiddelen: Sikuli, Lackey (Sikuli-compatibel, in pure Python), PyAutoGUI.

Toegankelijkheidstechnologieën

[+] De meest betrouwbare methode, omdat het mogelijk maakt om tekst te zoeken, ongeacht hoe deze door het systeem of framework wordt weergegeven.
[+] Maakt het mogelijk om tekstuele gegevens te extraheren => resultaten van tests eenvoudiger te verifiëren.
[+] Meestal de snelste, omdat het vrijwel geen CPU-resources verbruikt.
[-] Moeilijk om een cross-platform tool te maken: vrijwel alle open-source bibliotheken ondersteunen één of twee accessibility-technologieën. Windows/Linux/MacOS wordt helemaal niet ondersteund, behalve door betaalde tools zoals TestComplete, UFT of Squish.
[-] Niet altijd is zo'n technologie in principe beschikbaar. Bijvoorbeeld, testing van het opstartscherm binnen VirtualBox kan niet zonder beeldherkenning. Maar in veel klassieke gevallen is de accessibility-aanpak toch toepasbaar. Hierover gaat het verder.

Hulpmiddelen: TestStack.White in C#, Winium.Desktop in C# (Selenium-compatibel), MS WinAppDriver in C# (Appium-compatibel), pywinauto, pyatom (compatibel met LDTP), Python-UIAutomation-for-Windows, RAutomation in Ruby, LDTP (Linux Desktop Testing Project) en de Windows-versie ervan Cobra.

LDTP is misschien wel de enige cross-platform open-source tool (of eigenlijk een familie van bibliotheken) gebaseerd op accessibility-technologieën. Echter, het is niet erg populair. Ik heb het zelf niet gebruikt, maar volgens de opmerkingen is de interface niet de meest gebruiksvriendelijke. Als er positieve ervaringen zijn, deel deze dan alsjeblieft in de reacties.

Test backdoor (ook wel interne fiets genoemd)

Voor cross-platform applicaties maken de ontwikkelaars vaak een interne mechanismen om testbaarheid te waarborgen. Bijvoorbeeld, ze creëren een hulpprogramma TCP-server binnen de applicatie, waarop tests verbinden en tekstcommando's sturen: wat te klikken, waargegevens vandaan te halen, etc. Betrouwbaar, maar niet universeel.

Hoofd desktop toegankelijkheidstechnologieën

De oude vertrouwde Win32 API

De meeste Windows-applicaties, geschreven vóór de release van WPF en daarna Windows Store, zijn in meer of mindere mate gebaseerd op de Win32 API. Namelijk, MFC, WTL, C++ Builder, Delphi, VB6 — al deze tools gebruiken de Win32 API. Zelfs Windows Forms is in grote mate compatibel met de Win32 API.

Hulpmiddelen: AutoIt (vergelijkbaar met VB) en Python wrapper pyautoit, AutoHotkey (eigen taal, heeft IDispatch COM interface), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).

Microsoft UI Automation

Het grootste voordeel: de technologie MS UI Automation ondersteunt de overgrote meerderheid van GUI-applicaties op Windows met enkele zeldzame uitzonderingen. Probleem: het is niet veel gemakkelijker te leren dan Win32 API. Anders zou niemand wrappers eromheen maken.

Feitelijk is dit een set van custom COM interfaces (voornamelijk, UIAutomationCore.dll), en heeft ook een .NET-schil in de vorm van namespace System.Windows.Automation. Deze heeft overigens een ingebouwde bug waardoor sommige UI-elementen kunnen worden overgeslagen. Het is daarom beter om UIAutomationCore.dll direct te gebruiken (als je van UiaComWrapper op C# hebt gehoord, dat is het).

Varianten van COM interfaces:

(1) Basis IUknown — "de wortel van alle kwaad". De laagste laag, absoluut niet gebruiksvriendelijk.
(2) IDispatch en afgeleiden (bijvoorbeeld, Excel.Application), die in Python kunnen worden gebruikt met behulp van het pakket win32com.client (onderdeel van pyWin32). De meest handige en mooie optie.
(3) Custom interfaces, waarmee een derde Python-pakket comtypes.

Hulpmiddelen: TestStack.White in C#, pywinauto 0.6.0+, Winium.Desktop in C#, Python-UIAutomation-for-Windows (waarvan de broncode van de C-wrapper over UIAutomationCore.dll niet openbaar is), RAutomation op Ruby.

AT-SPI

Ondanks dat bijna alle distributies van het Linux-familie zijn gebouwd op het X Window System (in Fedora 25 zijn ‘de X’en vervangen door Wayland), kunnen ‘de X’en alleen omgaan met vensters op het bovenste niveau en muis/toetsenbord. Voor gedetailleerde analyses van knoppen, lijstvakken enzovoort — is er de technologie AT-SPI. De meest populaire windowmanagers hebben een zogenaamde AT-SPI-registry demon, die automatisch GUI voor applicaties waarborgt (minimaal worden Qt en GTK ondersteund).

Hulpmiddelen: pyatspi2.

pyatspi2 bevat naar mijn mening te veel afhankelijkheden zoals PyGObject. De technologie zelf is beschikbaar als een gewone dynamische bibliotheek libatspi.so. Het is bereikbaar. Referentiemanual. Voor de pywinauto-bibliotheek zijn we van plan ondersteuning voor AT-SPI te implementeren via het laden van libatspi.so en de ctypes-module. Er is alleen een klein probleem met het gebruik van de juiste versie, omdat deze een beetje verschilt tussen GTK+ en Qt-applicaties. Een verwachte release van pywinauto 0.7.0 met volledige ondersteuning voor Linux kan in de eerste helft van 2018 worden verwacht.

Apple Accessibility API

Op MacOS is er een eigen automatiseringstaal, AppleScript. Voor het realiseren van iets vergelijkbaars in Python moeten we natuurlijk functies uit ObjectiveC gebruiken. Sinds MacOS 10.6 is het bij de voor geïnstalleerde Python het pyobjc-pakket inbegrepen. Dit maakt ook de afhankelijkhedenlijst voor toekomstige ondersteuning in pywinauto eenvoudiger.

Tools: Naast de AppleScript-taal moet men aandacht besteden aan ATOMac, ook bekend als pyatom. Het is interface-compatibel met LDTP, maar is ook een zelfstandige bibliotheek. Er is een voorbeeld van automatisering van iTunes op macOs, geschreven door mijn student. Er is een bekend probleem: flexibele timing werkt niet (methoden waitFor*). Maar over het algemeen is het een leuke tool.

Hoe te beginnen met pywinauto

Ten eerste moet je gewapend zijn met een GUI-objectinspectietool (de zogenaamde Spy-tool). Deze helpt om de applicatie van binnenuit te onderzoeken: hoe de hiërarchie van elementen is opgebouwd en welke eigenschappen beschikbaar zijn. De meest bekende objectinspectoren zijn:

  • Spy++ — maakt deel uit van Visual Studio, inclusief de Express- of Community-editie. Het gebruikt de Win32 API. Zijn bekende kloon is AutoIt Window Info.
  • Inspect.exe — maakt deel uit van het Windows SDK. Als je het hebt geïnstalleerd, vind je het op 64-bits Windows in de map C:Program Files (x86)Windows Kitsbinx64. In de inspectietool moet je de modus UI Automation kiezen in plaats van MS AA (Active Accessibility, de voorloper van UI Automation).

Als we de applicatie doorlichten, kiezen we de backend die we gaan gebruiken. Het is voldoende om de naam van de backend op te geven bij het aanmaken van het Application-object.

  • backend=»win32″ — wordt voorlopig standaard gebruikt en werkt goed met MFC, WTL, VB6 en andere legacy-applicaties.
  • backend=»uia» — een nieuwe backend voor MS UI Automation: werkt perfect met WPF en WinForms; ook goed voor Delphi- en Windows Store-applicaties; werkt met Qt5 en sommige Java-applicaties. Over het algemeen, als Inspect.exe elementen en hun eigenschappen kan zien, betekent dit dat deze backend geschikt is. In principe ondersteunen de meeste browsers ook UI Automation (Mozilla standaard, en voor Chrome moet je de opdrachtregelparameter --force-renderer-accessibility, om elementen op pagina's in Inspect.exe te zien). Natuurlijk is concurrentie met Selenium op dit gebied nauwelijks mogelijk. Gewoon weer een manier om met de browser te werken (mogelijk nuttig voor cross-productscenario's).

Instapmogelijkheden voor automatisering

De applicatie is goed bestudeerd. Het is tijd om een Application-object te creëren en deze te starten of aan een al actieve te koppelen. Dit is niet gewoon een kloon van de standaardklasse. subprocess.Popen, maar meer een invoerobject dat al jouw acties beperkt binnen de grenzen van het proces. Dit is zeer nuttig als er meerdere exemplaren van de applicatie draaien en je de andere niet wilt raken.

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

# Laten we het venster beschrijven dat we willen vinden in het proces Notepad.exe
dlg_spec = app.UntitledNotepad
# wacht tot het venster daadwerkelijk verschijnt
actionable_dlg = dlg_spec.wait('visible')

Als je meerdere applicaties tegelijk wilt bedienen, helpt de klasse je daarbij. Desktop. Bijvoorbeeld, in de calculator op Win10 is de hiërarchie van elementen verspreid over meerdere processen (niet alleen calc.exe). Dus zonder object kan je niet verder. Desktop from subprocess import Popen from pywinauto import DesktopPopen('calc.exe', shell=True) dlg = Desktop(backend="uia").Calculator dlg.wait('visible')

Het wortelobject (

Application) — dit is de enige plek waar je de backend hoeft op te geven. Alles andere past transparant in het concept “specificatie->wrapper”, waar we het verder over zullen hebben. of DesktopDit is het belangrijkste concept waarop de interface van pywinauto is gebaseerd. Je kunt een venster/effect beschrijven, scherp of in algemene termen, zelfs als het nog niet bestaat of al gesloten is. De vensterspecificatie (object

Specificaties van vensters/elementen

WindowSpecification ) bewaarde de criteria waarmee je het werkelijke venster of element moet zoeken.Voorbeeld van een gedetailleerde vensterspecificatie:

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

De eigenlijke zoektocht naar het venster gebeurt door het aanroepen van de methode

.wrapper_object() . Hij retourneert een soort ‘wrapper’ voor het werkelijke venster/effect of gooitElementNotFoundError soms ElementAmbiguousError , als er meerdere elementen zijn gevonden, wat betekent dat het nodig is om de zoekcriteria te verduidelijken. Deze ‘wrapper’ kan al acties uitvoeren op het element of gegevens ervan ophalen.Python kan de aanroep verbergen, zodat de uiteindelijke code korter wordt. We raden aan dit alleen voor debugging te gebruiken. De volgende twee regels doen absoluut hetzelfde:

Python kan aanroep verbergen . Hij retourneert een soort ‘wrapper’ voor het werkelijke venster/effect of gooit, waardoor de uiteindelijke code korter wordt. We raden aan het alleen voor debugging te gebruiken. De volgende twee regels doen precies hetzelfde:

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

Er zijn veel zoekcriteria voor het venster specificeren. Hier zijn een paar voorbeelden:

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

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

Een lijst van alle mogelijke criteria is te vinden in de functie-documentatie. pywinauto.findwindows.find_elements(…).

De magie van toegang via attribuut en sleutel

Python vereenvoudigt het creëren van venster specificaties en herkent objectattributen dynamisch (de overschreven methode binnenin __getattribute__). Natuurlijk zijn er dezelfde beperkingen voor de naam van het attribuut als voor de naam van elke variabele (je kunt geen spaties, komma's en andere speciale symbolen invoegen). Gelukkig gebruikt pywinauto een zogenaamde "best match" zoekalgoritme dat bestand is tegen typfouten en kleine variaties.

app.UntitledNotepad
# hetzelfde als
app.window(best_match='UntitledNotepad')

Als je Unicode-strings nodig hebt (bijvoorbeeld voor het Russisch), spaties enz., kun je toegang krijgen via de sleutel (alsof het een gewone woordenlijst is):

app['Untitled - Notepad']
# hetzelfde als
app.window(best_match='Untitled - Notepad')

Vijf regels voor magische namen

Hoe weet je de referentiemagische namen? De namen die aan een element worden toegewezen voordat je zoekt. Als je een naam opgeeft die voldoende lijkt op de referentie, zal het element gevonden worden.

  1. Op basis van de titel (tekst, naam): app.Properties.OK.click()
  2. Op basis van tekst en type element: app.Properties.OKButton.click()
  3. Op basis van type en nummer: app.Properties.Button3.click() (namen Button0 en Button1 zijn gekoppeld aan het eerste gevonden element, Button2 — aan het tweede, en verder in volgorde — dat is historisch zo gegroeid)
  4. Op basis van statische tekst (aan de linkerkant of bovenaan) en type: app.OpenDialog.FileNameEdit.set_text("") (nuttig voor elementen met dynamische tekst)
  5. Op basis van type en tekst binnenin: app.Properties.TabControlSharing.select("Algemeen")

Gewoonlijk worden twee of drie regels tegelijk toegepast, zelden meer. Om te controleren welke specifieke namen beschikbaar zijn voor elk element, kun je de methode gebruiken print_control_identifiers(). Dit kan de boomstructuur van elementen zowel op het scherm als in een bestand afdrukken. Voor elk element worden de referentiemagische namen afgedrukt. Je kunt ook meer gedetailleerde specificaties van dochterelementen kopiëren en plakken. Het resultaat in het script zal er als volgt uitzien:

app.Properties.child_window(title="Bevat:", auto_id="13087", control_type="Bewerken")

De boomstructuur van elementen zelf - meestal een behoorlijk grote lap tekst.

>>> app.Properties.print_control_identifiers()

Controle-identificaties:

Dialoog - 'Windows NT-eigenschappen'    (L688, T518, R1065, B1006)
[u'Windows NT-eigenschappenDialoog', u'Dialoog', u'Windows NT-eigenschappen']
child_window(title="Windows NT-eigenschappen", control_type="Window")
   |
   | Afbeelding - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Afbeelding1', u'Afbeelding0', 'Afbeelding', u'1']
   | child_window(auto_id="13057", control_type="Afbeelding")
   |
   | Afbeelding - ''    (L717, T630, R1035, B632)
   | ['Afbeelding2', u'2']
   | child_window(auto_id="13095", control_type="Afbeelding")
   |
   | Bewerken - 'Mapnaam:'    (L790, T596, R1036, B619)
   | [u'3', 'Bewerken', u'Bewerken1', u'Bewerken0']
   | child_window(title="Mapnaam:", auto_id="13156", control_type="Edit")
   |
   | Statik - 'Type:'    (L717, T643, R780, B658)
   | [u'Type:Statik', u'Statik', u'Statik1', u'Statik0', u'Type:']
   | child_window(title="Type:", auto_id="13080", control_type="Text")
   |
   | Bewerken - 'Type:'    (L790, T643, R1036, B666)
   | [u'4', 'Bewerken2', u'Type:Bewerken']
   | child_window(title="Type:", auto_id="13059", control_type="Edit")
   |
   | Statik - 'Locatie:'    (L717, T669, R780, B684)
   | [u'Locatie:Statik', u'Locatie:', u'Statik2']
   | child_window(title="Locatie:", auto_id="13089", control_type="Text")
   |
   | Bewerken - 'Locatie:'    (L790, T669, R1036, B692)
   | ['Bewerken3', u'Locatie:Bewerken', u'5']
   | child_window(title="Locatie:", auto_id="13065", control_type="Edit")
   |
   | Statik - 'Grootte:'    (L717, T695, R780, B710)
   | [u'Grootte:Statik', u'Grootte:', u'Statik3']
   | child_window(title="Grootte:", auto_id="13081", control_type="Text")
   |
   | Bewerken - 'Grootte:'    (L790, T695, R1036, B718)
   | ['Bewerken4', u'6', u'Grootte:Bewerken']
   | child_window(title="Grootte:", auto_id="13064", control_type="Edit")
   |
   | Statik - 'Grootte op schijf:'    (L717, T721, R780, B736)
   | [u'Grootte op schijf:', u'Grootte op schijf:Statik', u'Statik4']
   | child_window(title="Grootte op schijf:", auto_id="13107", control_type="Text")
   |
   | Bewerken - 'Grootte op schijf:'    (L790, T721, R1036, B744)
   | ['Bewerken5', u'7', u'Grootte op schijf:Bewerken']
   | child_window(title="Grootte op schijf:", auto_id="13106", control_type="Edit")
   |
   | Statik - 'Bevat:'    (L717, T747, R780, B762)
   | [u'Bevat:1', u'Bevat:0', u'Bevat:Statik', u'Statik5', u'Bevat:']
   | child_window(title="Bevat:", auto_id="13088", control_type="Text")
   |
   | Bewerken - 'Bevat:'    (L790, T747, R1036, B770)
   | [u'8', 'Bewerken6', u'Bevat:Bewerken']
   | child_window(title="Bevat:", auto_id="13087", control_type="Edit")
   |
   | Afbeelding - 'Bevat:'    (L717, T773, R1035, B775)
   | [u'Bevat:Afbeelding', 'Afbeelding3', u'Bevat:2']
   | child_window(title="Bevat:", auto_id="13096", control_type="Afbeelding")
   |
   | Statik - 'Gemaakt:'    (L717, T786, R780, B801)
   | [u'Gemaakt:', u'Gemaakt:Statik', u'Statik6', u'Gemaakt:1', u'Gemaakt:0']
   | child_window(title="Gemaakt:", auto_id="13092", control_type="Text")
   |
   | Bewerken - 'Gemaakt:'    (L790, T786, R1036, B809)
   | [u'Gemaakt:Bewerken', 'Bewerken7', u'9']
   | child_window(title="Gemaakt:", auto_id="13072", control_type="Edit")
   |
   | Afbeelding - 'Gemaakt:'    (L717, T812, R1035, B814)
   | [u'Gemaakt:Afbeelding', 'Afbeelding4', u'Gemaakt:2']
   | child_window(title="Gemaakt:", auto_id="13097", control_type="Afbeelding")
   |
   | Statik - 'Attributen:'    (L717, T825, R780, B840)
   | [u'Attributen:Statik', u'Statik7', u'Attributen:']
   | child_window(title="Attributen:", auto_id="13091", control_type="Text")
   |
   | Checkbox - 'Schrijfbeschermd (geldt alleen voor bestanden in map)'    (L790, T825, R1035, B841)
   | [u'Checkbox0', u'Checkbox1', 'Checkbox', u'Schrijfbeschermd (geldt alleen voor bestanden in map)Checkbox', u'Schrijfbeschermd (geldt alleen voor bestanden in map)']
   | child_window(title="Schrijfbeschermd (geldt alleen voor bestanden in map)", auto_id="13075", control_type="CheckBox")
   |
   | Checkbox - 'Verborgen'    (L790, T848, R865, B864)
   | ['Checkbox2', u'VerborgenCheckbox', u'Verborgen']
   | child_window(title="Verborgen", auto_id="13076", control_type="CheckBox")
   |
   | Knop - 'Geavanceerd...'    (L930, T845, R1035, B868)
   | [u'Geavanceerd...', u'Geavanceerd...Knop', 'Knop', u'Knop1', u'Knop0']
   | child_window(title="Geavanceerd...", auto_id="13154", control_type="Knop")
   |
   | Knop - 'OK'    (L814, T968, R889, B991)
   | ['Knop2', u'OK', u'OKKnop']
   | child_window(title="OK", auto_id="1", control_type="Knop")
   |
   | Knop - 'Annuleren'    (L895, T968, R970, B991)
   | ['Knop3', u'AnnuleerKnop', u'Annuleren']
   | child_window(title="Annuleren", auto_id="2", control_type="Knop")
   |
   | Knop - 'Toepassen'    (L976, T968, R1051, B991)
   | ['Knop4', u'ToepassenKnop', u'Toepassen']
   | child_window(title="Toepassen", auto_id="12321", control_type="Knop")
   |
   | TabbladControle - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabbladControleDelen', u'TabbladControleEerdere Versies', u'TabbladControleBeveiliging', u'TabbladControle', u'TabbladControleAanpassen']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | Tabbladitem - 'Algemeen'    (L704, T558, R753, B576)
   |    | [u'AlgemeenTabbladItem', 'Tabbladitem', u'Algemeen', u'Tabbladitem0', u'Tabbladitem1']
   |    | child_window(title="Algemeen", control_type="TabbladItem")
   |    |
   |    | Tabbladitem - 'Delen'    (L753, T558, R801, B576)
   |    | [u'Delen', u'DelenTabbladItem', 'Tabbladitem2']
   |    | child_window(title="Delen", control_type="TabbladItem")
   |    |
   |    | Tabbladitem - 'Beveiliging'    (L801, T558, R851, B576)
   |    | [u'Beveiliging', 'Tabbladitem3', u'BeveiligingTabbladItem']
   |    | child_window(title="Beveiliging", control_type="TabbladItem")
   |    |
   |    | Tabbladitem - 'Eerdere Versies'    (L851, T558, R947, B576)
   |    | [u'Eerdere VersiesTabbladItem', u'Eerdere Versies', 'Tabbladitem4']
   |    | child_window(title="Eerdere Versies", control_type="TabbladItem")
   |    |
   |    | Tabbladitem - 'Aanpassen'    (L947, T558, R1007, B576)
   |    | [u'AanpassenTabbladItem', 'Tabbladitem5', u'Aanpassen']
   |    | child_window(title="Aanpassen", control_type="TabbladItem")
   |
   | Titelbalk - 'Geen'    (L712, T521, R1057, B549)
   | ['Titelbalk', u'11']
   |    |
   |    | Menu - 'Systeem'    (L696, T526, R718, B548)
   |    | [u'Systeem0', u'Systeem', u'Systeem1', u'Menu', u'SysteemMenu']
   |    | child_window(title="Systeem", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | Menu-item - 'Systeem'    (L696, T526, R718, B548)
   |    |    | [u'Systeem2', u'MenuItem', u'SysteemMenuItem']
   |    |    | child_window(title="Systeem", control_type="MenuItem")
   |    |
   |    | Knop - 'Sluiten'    (L1024, T519, R1058, B549)
   |    | [u'SluitenKnop', u'Sluiten', 'Knop5']
   |    | child_window(title="Sluiten", control_type="Knop")

In sommige gevallen kan het afdrukken van de hele boom traag zijn (bijvoorbeeld in iTunes met wel drie duizend elementen op één tabblad!), maar je kunt de parameter diepte (depth): diepte=1 — het element zelf, diepte=2 — alleen de directe kinderen, enzovoort. Dit kan ook worden opgegeven in specificaties bij het maken van child_window.

Voorbeelden

We blijven de lijst met voorbeelden in de repository aanvullen. Van de recente voorbeelden is de automatisering van de netwerk analyzer WireShark noemenswaard (dit is een goed voorbeeld van een Qt5-applicatie; hoewel deze taak ook zonder GUI kan worden opgelost, want er is scapy.Sniffer uit het Python-pakket scapy). Er is ook een voorbeeld van de automatisering van MS Paint met zijn Ribbon toolbar.

Nog een geweldig voorbeeld, geschreven door mijn student: een bestand slepen vanuit explorer.exe naar een Chrome-pagina voor Google Drive (het zal iets later naar de hoofdrepository worden verplaatst).

En natuurlijk, een voorbeeld van het abonneren op toetsenbord- en muisgebeurtenissen:
hook_and_listen.py.

Waardering

Een speciale dank gaat uit naar degenen die constant helpen om het project te ontwikkelen. Voor mij en Valentina is dit een constant hobby. Twee van mijn studenten aan de NNGU hebben onlangs hun bachelordiploma's over dit onderwerp behaald. Alexander heeft een grote bijdrage geleverd aan de ondersteuning van MS UI Automation en is onlangs begonnen met het maken van een automatische codegenerator volgens het principe 'opname-afspelen' op basis van tekstuele eigenschappen (dit is de moeilijkste functie), voorlopig alleen voor de 'uia' backend. Ivan ontwikkelt een nieuwe backend voor Linux op basis van AT-SPI (modules mouse en keyboard op basis van python-xlib — al in releases 0.6.x).

Aangezien ik al een tijdje een speciale cursus over automatisering met Python geef, voeren sommige masterstudenten hun huiswerk uit door kleine functies of automatiseringsvoorbeelden te implementeren. Sommige belangrijke zaken zijn onderhanden genomen door studenten tijdens het onderzoek. Hoewel ik soms streng moet letten op de kwaliteit van de code. Dit wordt sterk geholpen door statische analyzers (QuantifiedCode, Codacy en Landscape) en automatische tests in de cloud (service AppVeyor) met een code dekking van ongeveer 95%.

Ook dank aan iedereen die feedback achterlaat, bugs rapporteert en pull requests indient!

Aanvullende bronnen

We volgen vragen via de tag op StackOverflow (recentelijk is er een tag in de Russische versie van SO) en op het trefwoord op Toaster. Er is Russischtalige chat in Gitter.

Elke maand actualiseren we de ranglijst van open-source bibliotheken voor GUI-testautomatisering. Alleen Autohotkey (met een zeer grote gemeenschap en lange geschiedenis) en PyAutoGUI (vooral dankzij de populariteit van de boeken van Al Sweigart: 'Automate the Boring Stuff with Python' en andere) groeien sneller in het aantal sterren op GitHub.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster