Automatiseer desktop GUI in Python + pywinauto: hoe maak je vrienden met MS UI Automation

Python-bibliotheek pywinauto is een open source project om desktop GUI-applicaties op Windows te automatiseren. In de afgelopen twee jaar zijn er nieuwe belangrijke functies in verschenen:

  • Ondersteuning voor MS UI Automation-technologie. De interface is hetzelfde en wordt nu ondersteund: WinForms, WPF, Qt5, Windows Store (UWP) enzovoort - bijna alles wat op Windows staat.
  • Backend/plugin-systeem (nu zijn er twee onder de motorkap: default "win32" en een nieuwe "uia"). Dan gaan we soepel richting cross-platform.
  • Win32 hooks voor muis en toetsenbord (sneltoetsen in de geest van pyHook).

We zullen ook een klein overzicht maken van wat er in open source beschikbaar is voor desktopautomatisering (zonder de pretentie van een serieuze vergelijking).

Dit artikel is een gedeeltelijk transcript van een rapport van de SQA Days 20-conferentie in Minsk (video-opname и dia's), gedeeltelijk Russische versie Aan de slag voor pywinauto.

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

coördinaat methode

Hardcode klikpunten, hoop op goede hits.
[+] Platformoverschrijdend, eenvoudig te implementeren.
[+] Het is gemakkelijk om een ​​"record-replay"-record van tests te maken.
[-] Het meest onstabiel voor veranderingen in schermresolutie, thema, lettertypen, venstergroottes, enz.
[-] Er is veel inspanning nodig voor ondersteuning, het is vaak gemakkelijker om tests helemaal opnieuw te genereren of handmatig te testen.
[-] Automatiseert alleen acties, er zijn andere methoden voor het verifiëren en extraheren van gegevens.

Hulpmiddelen (platformonafhankelijk): autopsie, PyAutoGUI, PyGebruikersinvoer en vele anderen. In de regel bevatten complexere tools deze functionaliteit (niet altijd platformonafhankelijk).

Het is de moeite waard om te zeggen dat de coördinatenmethode andere benaderingen kan aanvullen. Voor aangepaste afbeeldingen kunt u bijvoorbeeld op relatieve coördinaten klikken (vanuit de linkerbovenhoek van het venster / element, en niet het hele scherm) - dit is meestal redelijk betrouwbaar, vooral als u rekening houdt met de lengte / breedte van de hele element (dan kunnen verschillende schermresoluties geen kwaad).

Een andere optie is om slechts één machine met stabiele instellingen toe te wijzen voor tests (niet platformonafhankelijk, maar in sommige gevallen is het goed).

Herkenning van referentiebeelden

[+] Platformoverschrijdend
[+-] Relatief betrouwbaar (beter dan de coördinatenmethode), maar vereist nog steeds trucs.
[-+] Relatief traag, omdat vereist CPU-bronnen voor herkenningsalgoritmen.
[-] Tekstherkenning (OCR) is in de regel uitgesloten => u kunt geen tekstgegevens ophalen. Voor zover ik weet, zijn de bestaande OCR-oplossingen niet erg betrouwbaar voor dit soort taken en worden ze niet veel gebruikt (welkom in de reacties als dit nog niet het geval is).

instrumenten: Sikuli, Lakei (Sikuli-compatibel, pure Python), PyAutoGUI.

toegankelijkheid technologie

[+] De meest betrouwbare methode, omdat stelt u in staat om naar tekst te zoeken, ongeacht hoe deze wordt weergegeven door het systeem of framework.
[+] Hiermee kunt u tekstgegevens extraheren => gemakkelijker om testresultaten te verifiëren.
[+] In de regel de snelste, omdat verbruikt bijna geen CPU-bronnen.
[-] Het is moeilijk om een ​​platformonafhankelijke tool te maken: absoluut alle open-sourcebibliotheken ondersteunen een of twee toegankelijkheidstechnologieën. Windows/Linux/MacOS wordt door niemand volledig ondersteund, behalve betaalde zoals TestComplete, UFT of Squish.
[-] Dergelijke technologie is in principe niet altijd beschikbaar. Zo is het testen van het opstartscherm binnen VirtualBox onontbeerlijk zonder beeldherkenning. Maar in veel klassieke gevallen is de toegankelijkheidsbenadering nog steeds van toepassing. Over het verder en zal worden besproken.

instrumenten: TestStack.Wit in C# Winium.Desktop in C# (Selenium-compatibel), MS WinApp-stuurprogramma in C# (Appium-compatibel), pywinauto, pyatom (compatibel met LDTP), Python-UIAutomation-voor-Windows, RAutomatisering in robijn, LDTP (Linux Desktop Testing Project) en de Windows-versie Cobra.

LDTP is misschien wel de enige platformonafhankelijke open-sourcetool (preciezer gezegd: een familie van bibliotheken) op basis van toegankelijkheidstechnologieën. Hij is echter niet erg populair. Ik heb het zelf niet gebruikt, maar volgens reviews is de interface niet de handigste. Als er positieve beoordelingen zijn, deel deze dan in de opmerkingen.

Test de achterdeur (ook bekend als de binnenkant van de fiets)

Voor platformonafhankelijke applicaties maken de ontwikkelaars vaak zelf een intern mechanisme om de testbaarheid te garanderen. Ze maken bijvoorbeeld een service-TCP-server in de applicatie, testen er verbinding mee en sturen tekstopdrachten: waarop te klikken, waar gegevens vandaan te halen, enz. Betrouwbaar, maar niet universeel.

Belangrijkste technologieën voor desktoptoegankelijkheid

Goede oude Win32 API

De meeste Windows-applicaties die zijn geschreven vóór de release van WPF en vervolgens de Windows Store, zijn op de een of andere manier gebouwd op de Win32 API. Namelijk MFC, WTL, C++ Builder, Delphi, VB6 - al deze tools gebruiken de Win32 API. Zelfs Windows Forms zijn grotendeels Win32 API-compatibel.

instrumenten: AutoIt (vergelijkbaar met VB) en Python-wrapper pyautoit, AutoHotkey (eigen taal, er is een IDispatch COM-interface), pywinauto (Python) RAutomatisering (Robijn), win32-autogui (Robijn).

Microsoft UI-automatisering

Het belangrijkste pluspunt: MS UI Automation-technologie ondersteunt de overgrote meerderheid van GUI-applicaties op Windows, met zeldzame uitzonderingen. Probleem: het is niet veel gemakkelijker te leren dan de Win32 API. Anders zou niemand er wikkels omheen maken.

In feite is dit een set aangepaste COM-interfaces (voornamelijk UIAutomationCore.dll) en heeft ook een .NET-wrapper in het formulier namespace System.Windows.Automation. Het heeft trouwens een geïntroduceerde bug, waardoor sommige UI-elementen kunnen worden overgeslagen. Daarom is het beter om UIAutomationCore.dll rechtstreeks te gebruiken (als je hebt gehoord over UiaComWrapper in C #, dan is dit het).

Soorten COM-interfaces:

(1) Base waarvan bekend is dat het "de wortel van alle kwaad" is. Het meest laagdrempelige, nooit gebruiksvriendelijk.
(2) IDispatch en afgeleiden (bijvoorbeeld Excel.Application) die in Python kan worden gebruikt met behulp van het pakket win32com.client (inbegrepen bij pyWin32). De handigste en mooiste optie.
(3) Aangepaste interfaces waarmee een Python-pakket van derden kan werken comtypes.

instrumenten: TestStack.Wit in C# pywinauto 0.6.0 jaar of ouder+, Winium.Desktop in C# Python-UIAutomation-voor-Windows (hun broncode voor sish wrappers over UIAutomationCore.dll wordt niet bekendgemaakt), RAutomatisering op Ruby.

AT-SPI

Ondanks het feit dat bijna alle assen van de Linux-familie zijn gebouwd op het X Window-systeem (in Fedora 25 werden de X's veranderd in Wayland), kun je met de X's alleen werken met vensters op het hoogste niveau en een muis / toetsenbord. Voor een gedetailleerde analyse van knoppen, keuzelijsten, enzovoort, is er AT-SPI-technologie. De meest populaire windowmanagers hebben een zogenaamde AT-SPI-registerdaemon, die een geautomatiseerde GUI voor applicaties biedt (tenminste Qt en GTK worden ondersteund).

instrumenten: pyatspi2.

pyatspi2 bevat naar mijn mening te veel afhankelijkheden zoals hetzelfde PyGObject. De technologie zelf is beschikbaar als een reguliere dynamische bibliotheek libatspi.so. Ze heeft Naslaggids. Voor de pywinauto-bibliotheek zijn we van plan AT-SPI-ondersteuning als volgt te implementeren: door libatspi.so en de ctypes-module te laden. Er is alleen een klein probleem bij het gebruik van de juiste versie, omdat ze voor GTK + en Qt-applicaties iets anders zijn. De waarschijnlijke release van pywinauto 0.7.0 met volledige Linux-ondersteuning kan in de eerste helft van 2018 worden verwacht.

Apple Toegankelijkheids-API

MacOS heeft zijn eigen automatiseringstaal, AppleScript. Om zoiets in Python te implementeren, moet je natuurlijk functies van ObjectiveC gebruiken. Beginnend lijkt het erop dat zelfs met MacOS 10.6 het pyobjc-pakket is opgenomen in de vooraf geïnstalleerde python. Dit maakt het ook gemakkelijker om afhankelijkheden op te sommen voor toekomstige ondersteuning in pywinauto.

Hulpmiddelen: Naast de Apple Script-taal moet u op letten ATOMac, ook wel pyatom genoemd. Het is interface-compatibel met LDTP, maar is ook een op zichzelf staande bibliotheek. Het heeft iTunes-automatiseringsvoorbeeld op macOSgeschreven door mijn leerling. Er is een bekend probleem: flexibele timings werken niet (methods waitFor*). Maar over het algemeen een goede zaak.

Hoe aan de slag te gaan met pywinauto

De eerste stap is om jezelf uit te rusten met een GUI-objectinspecteur (de zogenaamde Spy-tool). Het zal helpen om de applicatie van binnenuit te bestuderen: hoe de hiërarchie van elementen is gerangschikt, welke eigenschappen beschikbaar zijn. De bekendste objectinspecteurs zijn:

  • spion++ - inbegrepen bij Visual Studio, inclusief Express of Community Edition. Maakt gebruik van Win32-API. Ook wel kloon genoemd AutoIt-vensterinfo.
  • Inspect.exe - Opgenomen in de Windows SDK. Als je het hebt geïnstalleerd, kun je het in 64-bits Windows in de map vinden C:Program Files (x86)Windows Kits<winver>binx64. In de inspecteur zelf moet u de modus selecteren UI-automatisering in plaats van MS AA (Active Accessibility, voorloper van UI Automation).

Nadat we de applicatie door en door hebben verlicht, selecteren we de backend die we zullen gebruiken. Het is voldoende om de naam van de backend op te geven bij het maken van het Application-object.

  • backend="win32" - hoewel standaard gebruikt, werkt het goed met MFC, WTL, VB6 en andere legacy-applicaties.
  • backend="uia" - 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-toepassingen. En in het algemeen, als Inspect.exe de elementen en hun eigenschappen ziet, dan is deze backend geschikt. In principe ondersteunen de meeste browsers ook UI Automation (standaard Mozilla, terwijl Chrome bij het opstarten de opdrachtregeltoets moet invoeren --force-renderer-accessibilityom items op pagina's in Inspect.exe te zien). Natuurlijk is concurrentie met Selenium op dit gebied nauwelijks mogelijk. Gewoon een andere manier om met de browser te werken (kan handig zijn voor een cross-productscenario).

Toegangspunten voor automatisering

De app is goed onderzocht. Het is tijd om een ​​Application-object te maken en uit te voeren, of te koppelen aan een object dat al actief is. Het is niet alleen een kloon van de standaardklasse subprocess.Popen, wat een inleidend object is dat al uw acties beperkt tot de grenzen van het proces. Dit is erg handig als er meerdere exemplaren van een toepassing actief zijn en u de rest niet wilt aanraken.

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

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

Als u meerdere applicaties tegelijk wilt beheren, zal de klas u helpen Desktop. In de rekenmachine op Win10 is de hiërarchie van elementen bijvoorbeeld verspreid over meerdere processen (niet alleen calc.exe). Dus geen voorwerp Desktop niet genoeg.

from subprocess import Popen
from pywinauto import Desktop

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

Root-object (Application of Desktop) is de enige plaats waar u de backend moet specificeren. Al het andere valt transparant onder het concept van "specificatie-> wikkel", dat later zal worden besproken.

Raam/element specificaties

Dit is het kernconcept waarop de pywinauto-interface is gebouwd. U kunt het venster/element globaal of gedetailleerder beschrijven, ook als het nog niet bestaat of al gesloten is. vensterspecificatie (object raam specificatie) slaat de criteria op waarmee u naar een echt venster of element moet zoeken.

Een voorbeeld van een gedetailleerde raamspecificatie:

>>> 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>

Het zoeken naar het venster zelf vindt plaats door de methode aan te roepen .wrapper_object(). Het retourneert een "wrapper" voor een echt venster/element of worpen ElementNotFoundError (soms ElementAmbiguousError, als er meerdere elementen worden gevonden, dat wil zeggen, u moet de zoekcriteria verfijnen). Deze "wrapper" weet al hoe hij bepaalde acties met het element moet uitvoeren of er gegevens van kan ontvangen.

Python kan de oproep verbergen .wrapper_object(), zodat de uiteindelijke code korter wordt. We raden aan om het alleen voor foutopsporingsdoeleinden te gebruiken. De volgende twee regels doen precies hetzelfde:

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

Er zijn verschillende zoekcriteria voor een vensterspecificatie. Hier zijn slechts enkele voorbeelden:

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

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

De lijst met alle mogelijke criteria staat in de functie docks pywinauto.findwindows.find_elements(...).

De magie van toegang via attribuut en sleutel

Python maakt het eenvoudig om vensterspecificaties te maken en objectattributen dynamisch te herkennen (intern overschreven __getattribute__). Natuurlijk gelden voor de attribuutnaam dezelfde beperkingen als voor de naam van een willekeurige variabele (u kunt geen spaties, komma's en andere speciale tekens invoegen). Gelukkig gebruikt pywinauto een zogenaamd "best match" zoekalgoritme dat bestand is tegen typefouten en kleine variaties.

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

Als je nog steeds Unicode-strings nodig hebt (bijvoorbeeld voor de Russische taal), spaties, etc., kun je toegang krijgen met een sleutel (alsof het een gewoon woordenboek is):

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

Vijf regels voor magische namen

Hoe de referentie magische namen te achterhalen? Degenen die vóór de zoekopdracht aan het element zijn toegewezen. Als je een naam hebt opgegeven die voldoende lijkt op het sjabloon, dan wordt het element gevonden.

  1. Op titel (tekst, naam): app.Properties.OK.click()
  2. Op tekst en op elementtype: app.Properties.OKButton.click()
  3. Op type en op nummer: app.Properties.Button3.click() (namen Button0 и Button1 gebonden aan het eerst gevonden element, Button2 - tot de tweede, en dan in volgorde - het gebeurde historisch)
  4. Op statische tekst (links of bovenaan) en op type: app.OpenDialog.FileNameEdit.set_text("") (handig voor elementen met dynamische tekst)
  5. Op type en op tekst binnenin: app.Properties.TabControlSharing.select("General")

Gewoonlijk worden twee of drie regels tegelijkertijd toegepast, zelden meer. Om te controleren welke specifieke namen voor elk element beschikbaar zijn, kunt u de methode gebruiken print_control_identifiers(). Het kan een boom van elementen zowel naar het scherm als naar een bestand afdrukken. Voor elk element worden de magische referentienamen afgedrukt. U kunt vanaf daar ook meer gedetailleerde specificaties van onderliggende elementen kopiëren en plakken. Het resultaat in het script ziet er als volgt uit:

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

De boom der elementen zelf is meestal een vrij groot voetendoek.

>>> 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")

In sommige gevallen kan het afdrukken van de hele boom vertragen (in iTunes staan ​​er bijvoorbeeld maar liefst drieduizend items op één tabblad!), Maar u kunt de parameter depth (diepte): depth=1 - het onderdeel zelf depth=2 - alleen directe kinderen, enzovoort. Het kan ook worden gespecificeerd in de specificaties bij het maken child_window.

Примеры

We vullen constant aan lijst met voorbeelden in de repository. Van de nieuwe is het vermeldenswaard de automatisering van de WireShark-netwerkanalysator (dit is een goed voorbeeld van een Qt5-applicatie; hoewel deze taak kan worden opgelost zonder een GUI, omdat er scapy.Sniffer van python-pakket scapey). Er is ook een voorbeeld van MS Paint-automatisering met de lintwerkbalk.

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

En natuurlijk een voorbeeld van abonneren op toetsenbord- (sneltoetsen) en muisgebeurtenissen:
hook_and_listen.py.

Dankbetuigingen

Speciale dank aan degenen die constant helpen om het project te ontwikkelen. Voor mij en Valentina het is een doorlopende hobby. Twee van mijn studenten van UNN hebben onlangs hun bachelor in dit onderwerp afgerond. Alexander heeft een grote bijdrage geleverd aan de ondersteuning van MS UI Automation en is onlangs begonnen met het maken van een automatische codegenerator op basis van het "record-play"-principe op basis van teksteigenschappen (dit is de moeilijkste functie), tot nu toe alleen voor de "uia"-backend. Ivan ontwikkelt een nieuwe backend voor Linux op basis van AT-SPI (modules mouse и keyboard gebaseerd op python-xlib - al in releases 0.6.x).

Omdat ik al geruime tijd een speciale cursus over automatisering in Python geef, doen sommige masterstudenten hun huiswerk, waarbij ze kleine functies of voorbeelden van automatisering implementeren. Enkele belangrijke dingen in de onderzoeksfase zijn ook ooit door studenten opgegraven. Al moet je soms wel streng toezien op de kwaliteit van de code. Dit wordt enorm geholpen door statische analysers (QuantifiedCode, Codacy en Landscape) en geautomatiseerde tests in de cloud (AppVeyor-service) met een codedekking van ongeveer 95%.

Ook bedankt aan iedereen die feedback achterlaat, bugs start en pull-aanvragen verstuurt!

Aanvullende bronnen

We volgen vragen op tag op StackOverflow (onlangs verschenen tag in de Russische versie van SO) En op trefwoord op Toaster. er bestaat Russische chat in Gitter.

We updaten elke maand beoordeling van open-sourcebibliotheken voor GUI-testen. In termen van het aantal sterren op de github, alleen Autohotkey (ze hebben een zeer grote gemeenschap en een lange geschiedenis) en PyAutoGUI (grotendeels vanwege de populariteit van boeken van de auteur Al Sweigart: "Automate the Boring Stuff with Python" en anderen) groeien sneller.

Bron: www.habr.com

Koop betrouwbare hosting voor sites met DDoS-bescherming, VPS VDS-servers 🔥 Koop betrouwbare websitehosting met DDoS-bescherming, VPS- en VDS-servers | ProHoster