Automatiser desktop GUI i Python + pywinauto: hvordan man bliver venner med MS UI Automation

Python bibliotek pywinauto er et open source-projekt til at automatisere desktop GUI-applikationer på Windows. I løbet af de sidste to år er der dukket nye store funktioner op i den:

  • Understøttelse af MS UI Automation-teknologi. Grænsefladen er den samme, og understøttes nu: WinForms, WPF, Qt5, Windows Store (UWP) og så videre - næsten alt hvad der er på Windows.
  • Backend/plugin-system (nu er der to af dem under motorhjelmen: standard "win32" og nyt "uia"). Så bevæger vi os jævnt mod tværplatforme.
  • Win32 kroge til mus og tastatur (genvejstaster i pyHooks ånd).

Vi vil også lave en lille oversigt over, hvad der er tilgængeligt i open source til desktop automation (uden forudsætninger for en seriøs sammenligning).

Denne artikel er en delvis udskrift af en rapport fra SQA Days 20-konferencen i Minsk (videooptagelse и dias), delvis russisk version Introduktion til pywinauto.

Lad os starte med en kort oversigt over open source på dette område. For desktop GUI-applikationer er tingene noget mere komplicerede end for nettet, som har Selenium. Her er de vigtigste tilgange:

koordinere metode

Hardcode klikpunkter, håber på gode hits.
[+] Cross-platform, let at implementere.
[+] Det er nemt at lave en "record-replay" registrering af tests.
[-] Den mest ustabile over for ændringer i skærmopløsning, tema, skrifttyper, vinduesstørrelser osv.
[-] Der kræves en stor indsats for at støtte, det er ofte nemmere at regenerere tests fra bunden eller teste manuelt.
[-] Automatiserer kun handlinger, der er andre metoder til at verificere og udtrække data.

Værktøjer (på tværs af platforme): autokopiering, PyAutoGUI, PyUserInput og mange andre. Som regel omfatter mere komplekse værktøjer denne funktionalitet (ikke altid på tværs af platforme).

Det er værd at sige, at koordinatmetoden kan supplere andre tilgange. For eksempel, for brugerdefineret grafik, kan du klikke på relative koordinater (fra det øverste venstre hjørne af vinduet / elementet, og ikke hele skærmen) - dette er normalt ret pålideligt, især hvis du tager højde for længden / bredden af hele elementet (så vil forskellige skærmopløsninger ikke skade).

En anden mulighed er kun at tildele én maskine med stabile indstillinger til test (ikke på tværs af platforme, men i nogle tilfælde er det godt).

Genkendelse af referencebilleder

[+] På tværs af platforme
[+-] Relativt pålideligt (bedre end koordinatmetoden), men kræver stadig tricks.
[-+] Relativt langsomt, fordi kræver CPU-ressourcer til genkendelsesalgoritmer.
[-] Tekstgenkendelse (OCR) er som regel udelukket => du kan ikke få tekstdata. Så vidt jeg ved, er de eksisterende OCR-løsninger ikke særlig pålidelige til denne type opgaver, og er ikke meget brugte (velkommen i kommentarerne, hvis dette ikke allerede er tilfældet).

instrumenter: Sikuli, Lackey (Sikuli-kompatibel, ren Python), PyAutoGUI.

tilgængelighedsteknologi

[+] Den mest pålidelige metode, fordi giver dig mulighed for at søge efter tekst, uanset hvordan den gengives af systemet eller rammen.
[+] Giver dig mulighed for at udtrække tekstdata => nemmere at verificere testresultater.
[+] Som regel den hurtigste, fordi bruger næsten ingen CPU-ressourcer.
[-] Det er svært at lave et værktøj på tværs af platforme: Absolut alle open source-biblioteker understøtter en eller to tilgængelighedsteknologier. Windows/Linux/MacOS er ikke fuldt understøttet af nogen undtagen betalte som TestComplete, UFT eller Squish.
[-] Sådan teknologi er i princippet ikke altid tilgængelig. For eksempel er det uundværligt at teste boot-skærmen inde i VirtualBox uden billedgenkendelse. Men i mange klassiske tilfælde er tilgængelighedstilgangen stadig gældende. Om det yderligere og vil blive diskuteret.

instrumenter: TestStack.White i C# Winium.Desktop i C# (Selenium-kompatibel), MS WinApp-driver i C# (Appium-kompatibel), pywinauto, pyatom (kompatibel med LDTP), Python-UIAutomation-til-Windows, RAutomation i Ruby LDTP (Linux Desktop Testing Project) og dets Windows-version Cobra.

LDTP er måske det eneste open source-værktøj på tværs af platforme (mere præcist, en familie af biblioteker) baseret på tilgængelighedsteknologier. Han er dog ikke særlig populær. Jeg har ikke selv brugt det, men ifølge anmeldelser er grænsefladen ikke den mest bekvemme. Hvis der er positive anmeldelser, så del venligst i kommentarerne.

Test bagdør (alias indvendig cykel)

For applikationer på tværs af platforme laver udviklerne ofte selv en intern mekanisme for at sikre testbarhed. For eksempel opretter de en tjeneste TCP-server i applikationen, tester forbinder til den og sender tekstkommandoer: hvad skal man klikke på, hvor man henter data fra osv. Pålidelig, men ikke universel.

Vigtigste desktop-tilgængelighedsteknologier

Godt gammelt Win32 API

De fleste Windows-applikationer skrevet før udgivelsen af ​​WPF og derefter Windows Store er bygget på Win32 API på en eller anden måde. Nemlig MFC, WTL, C++ Builder, Delphi, VB6 - alle disse værktøjer bruger Win32 API. Selv Windows Forms er stort set Win32 API-kompatible.

instrumenter: AutoIt (svarende til VB) og Python-indpakning pyautoit, AutoHotkey (eget sprog, der er en IDispatch COM-grænseflade), pywinauto (Python) RAutomation (Rubin), win32-autogui (Rubin).

Microsoft UI Automation

Det største plus: MS UI Automation-teknologi understøtter langt de fleste GUI-applikationer på Windows med sjældne undtagelser. Problem: Det er ikke meget nemmere at lære end Win32 API. Ellers ville ingen lave indpakninger over det.

Faktisk er dette et sæt brugerdefinerede COM-grænseflader (hovedsageligt UIAutomationCore.dll) og har også en .NET-indpakning i formularen namespace System.Windows.Automation. Forresten har den en indført fejl, på grund af hvilken nogle UI-elementer kan springes over. Derfor er det bedre at bruge UIAutomationCore.dll direkte (hvis du hørte om UiaComWrapper i C #, så er det det).

Varianter af COM-grænseflader:

(1) Base IUkendt er "roden til alt ondt". Det mest lave niveau, aldrig brugervenligt.
(2) ID-afsendelse og derivater (f.eks. Excel.Application), der kan bruges i Python ved hjælp af win32com.client-pakken (inkluderet med pyWin32). Den mest bekvemme og smukke mulighed.
(3) Brugerdefinerede grænseflader, som en tredjeparts Python-pakke kan arbejde med komtyper.

instrumenter: TestStack.White i C# pywinauto 0.6.0 +, Winium.Desktop i C# Python-UIAutomation-til-Windows (deres kildekode til sish wrappers over UIAutomationCore.dll afsløres ikke), RAutomation på Ruby.

AT-SPI

På trods af det faktum, at næsten alle akser i Linux-familien er bygget på X Window System (i Fedora 25 blev X'erne ændret til Wayland), tillader X'erne dig kun at betjene med vinduer på øverste niveau og en mus/tastatur. For en detaljeret analyse af knapper, listebokse og så videre er der AT-SPI-teknologi. De mest populære vinduesadministratorer har en såkaldt AT-SPI registry daemon, som giver en automatiseret GUI til applikationer (mindst Qt og GTK er understøttet).

instrumenter: pyatspi2.

pyatspi2, efter min mening, indeholder for mange afhængigheder som det samme PyGObject. Selve teknologien er tilgængelig som et almindeligt dynamisk bibliotek libatspi.so. Hun har Referencehåndbog. For pywinauto-biblioteket planlægger vi at implementere AT-SPI-understøttelse som følger: gennem indlæsning af libatspi.so og ctypes-modulet. Der er kun et lille problem med at bruge den rigtige version, fordi for GTK + og Qt applikationer er de lidt anderledes. Den sandsynlige udgivelse af pywinauto 0.7.0 med fuld Linux-understøttelse kan forventes i første halvdel af 2018.

Apple Accessibility API

MacOS har sit eget automatiseringssprog, AppleScript. For at implementere sådan noget i Python skal du selvfølgelig bruge funktioner fra ObjectiveC. Fra og med, ser det ud til, at selv med MacOS 10.6 er pyobjc-pakken inkluderet i den forudinstallerede python. Dette vil også gøre det lettere at liste afhængigheder til fremtidig support i pywinauto.

Værktøjer: Ud over Apple Script-sproget bør du være opmærksom på ATOMac, alias pyatom. Det er interface-kompatibelt med LDTP, men er også et selvstændigt bibliotek. Det har iTunes-automatiseringseksempel på macOSskrevet af min elev. Der er et kendt problem: fleksible tidspunkter virker ikke (metoder waitFor*). Men generelt set en god ting.

Sådan kommer du i gang med pywinauto

Det første trin er at udstyre dig selv med en GUI-objektinspektør (det der kaldes Spy-værktøjet). Det vil hjælpe med at studere applikationen indefra: hvordan hierarkiet af elementer er arrangeret, hvilke egenskaber der er tilgængelige. De mest berømte objektinspektører er:

  • Spy++ - inkluderet i Visual Studio, inklusive Express eller Community Edition. Bruger Win32 API. Også kendt som en klon Info om AutoIt-vindue.
  • Inspect.exe - Inkluderet i Windows SDK. Hvis du har det installeret, så kan du på 64-bit Windows finde det i mappen C:Program Files (x86)Windows Kits<winver>binx64. I selve inspektøren skal du vælge tilstanden UI-automatisering i stedet for MS AA (Active Accessibility, forfader til UI Automation).

Efter at have oplyst applikationen igennem og igennem, vælger vi den backend, vi vil bruge. Det er nok at angive navnet på backend, når du opretter Application-objektet.

  • backend="win32" - Mens det bruges som standard, fungerer det godt med MFC, WTL, VB6 og andre ældre applikationer.
  • backend="uia" - ny backend til MS UI Automation: fungerer perfekt med WPF og WinForms; også god til Delphi og Windows Store-applikationer; fungerer med Qt5 og nogle Java-applikationer. Og generelt, hvis Inspect.exe ser elementerne og deres egenskaber, så er denne backend velegnet. I princippet understøtter de fleste browsere også UI Automation (Mozilla som standard, mens Chrome skal fodre kommandolinjetasten ved opstart --force-renderer-accessibilityfor at se elementer på sider i Inspect.exe). Selvfølgelig er konkurrence med Selen på dette område næppe mulig. Bare endnu en måde at arbejde med browseren på (kan være nyttig i et scenarie på tværs af produkter).

Indgangspunkter for automatisering

Appen er blevet undersøgt godt. Det er tid til at oprette et Application-objekt og køre det, eller vedhæfte til et allerede kørende. Det er ikke kun en klon af standardklassen subprocess.Popen, som er et indledende objekt, der begrænser alle dine handlinger til processens grænser. Dette er meget nyttigt, hvis der kører flere forekomster af et program, og du ikke ønsker at røre ved resten.

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

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

Hvis du vil administrere flere applikationer på én gang, hjælper klassen dig Desktop. For eksempel, i lommeregneren på Win10 er hierarkiet af elementer spredt over flere processer (ikke kun calc.exe). Så ingen genstand Desktop ikke nok.

from subprocess import Popen
from pywinauto import Desktop

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

Rodobjekt (Application eller Desktop) er det eneste sted, hvor du skal angive backend. Alt andet falder gennemsigtigt ind under begrebet "specifikation-> indpakning", som vil blive diskuteret senere.

Vindue/element specifikationer

Dette er kernekonceptet, som pywinauto-grænsefladen er bygget på. Du kan beskrive vinduet/elementet groft eller mere detaljeret, selvom det endnu ikke eksisterer eller allerede er lukket. vinduesspecifikation (objekt vinduesspecifikation) gemmer de kriterier, som du skal bruge til at søge efter et rigtigt vindue eller element.

Et eksempel på en detaljeret vinduesspecifikation:

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

Søgningen efter selve vinduet sker ved at kalde metoden .wrapper_object(). Det returnerer noget "indpakning" for et rigtigt vindue/element eller kast ElementNotFoundError (Sommetider ElementAmbiguousError, hvis der findes flere elementer, det vil sige, at du skal forfine søgekriterierne). Denne "wrapper" ved allerede, hvordan man udfører nogle handlinger med elementet eller modtager data fra det.

Python kan skjule opkaldet .wrapper_object(), så den endelige kode bliver kortere. Vi anbefaler kun at bruge det til fejlretningsformål. De næste to linjer gør nøjagtig det samme:

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

Der er en række søgekriterier for en vinduesspecifikation. Her er blot nogle få eksempler:

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

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

Listen over alle mulige kriterier findes i funktionsdokken pywinauto.findwindows.find_elements(...).

Magien ved adgang efter egenskab og nøgle

Python gør det nemt at oprette vinduesspecifikationer og genkende objektattributter dynamisk (internt tilsidesat __getattribute__). Selvfølgelig er de samme begrænsninger pålagt attributnavnet som på navnet på enhver variabel (du kan ikke indsætte mellemrum, kommaer og andre specialtegn). Heldigvis bruger pywinauto en såkaldt "best match" søgealgoritme, der er modstandsdygtig overfor slåfejl og små variationer.

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

Hvis du stadig har brug for Unicode-strenge (for eksempel til det russiske sprog), mellemrum osv., kan du få adgang med tasten (som om det var en almindelig ordbog):

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

Fem regler for magiske navne

Hvordan finder man ud af de magiske referencenavne? Dem, der er tildelt elementet før søgningen. Hvis du har angivet et navn, der minder tilstrækkeligt om skabelonen, vil elementet blive fundet.

  1. Efter titel (tekst, navn): app.Properties.OK.click()
  2. Efter tekst og efter elementtype: app.Properties.OKButton.click()
  3. Efter type og nummer: app.Properties.Button3.click() (navne Button0 и Button1 bundet til det første fundne element, Button2 - til den anden, og så i rækkefølge - det skete historisk)
  4. Ved statisk tekst (venstre eller øverst) og efter type: app.OpenDialog.FileNameEdit.set_text("") (nyttigt for elementer med dynamisk tekst)
  5. Efter type og efter tekst inde: app.Properties.TabControlSharing.select("General")

Normalt anvendes to eller tre regler på samme tid, sjældent flere. For at kontrollere, hvilke specifikke navne der er tilgængelige for hvert element, kan du bruge metoden print_control_identifiers(). Det kan udskrive et træ af elementer både til skærmen og til en fil. For hvert element udskrives dets magiske referencenavne. Du kan også kopiere og indsætte mere detaljerede specifikationer for underordnede elementer derfra. Resultatet i scriptet vil se således ud:

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

Træet af elementer i sig selv er normalt en ret stor foddug.

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

I nogle tilfælde kan udskrivning af hele træet blive langsommere (for eksempel i iTunes er der så mange som tre tusinde elementer på en fane!), Men du kan bruge parameteren depth (dybde): depth=1 - selve elementet depth=2 — kun umiddelbare børn og så videre. Det kan også angives i specifikationerne ved oprettelse child_window.

Примеры

Vi fylder hele tiden op liste over eksempler i depotet. Af de friske er det værd at bemærke automatiseringen af ​​WireShark-netværksanalysatoren (dette er et godt eksempel på en Qt5-applikation; selvom denne opgave kan løses uden en GUI, fordi der er scapy.Sniffer fra python-pakken skurrende). Der er også et eksempel på MS Paint-automatisering med dens Ribbon-værktøjslinje.

Endnu et godt eksempel skrevet af min elev: trækker filen fra explorer.exe til chrome-siden til google drev (det vil migrere til hovedlageret lidt senere).

Og selvfølgelig et eksempel på at abonnere på tastatur (genvejstaster) og musebegivenheder:
hook_and_listen.py.

Tak

Særlig tak til dem, der konstant er med til at udvikle projektet. For mig og Valentina det er en vedvarende hobby. To af mine studerende fra UNN afsluttede for nylig deres bachelor i dette emne. Alexander ydet et stort bidrag til MS UI Automation support og begyndte for nylig at lave en automatisk kodegenerator baseret på "record-play" princippet baseret på tekstegenskaber (dette er den sværeste funktion), indtil videre kun for "uia" backend. Ivan udvikler en ny backend til Linux baseret på AT-SPI (moduler mouse и keyboard baseret på python-xlib - allerede i 0.6.x-udgivelser).

Da jeg i et stykke tid har undervist i et særligt kursus om automatisering i Python, laver nogle af kandidatstuderende deres hjemmearbejde, implementerer små funktioner eller eksempler på automatisering. Nogle vigtige ting på forskningsstadiet blev også engang gravet frem af studerende. Selvom du nogle gange skal nøje overvåge kodens kvalitet. Dette er i høj grad hjulpet af statiske analysatorer (QuantifiedCode, Codacy og Landscape) og automatiserede tests i skyen (AppVeyor service) med kodedækning på omkring 95%.

Også tak til alle, der giver feedback, starter fejl og sender pull-anmodninger!

Yderligere ressourcer

Vi følger spørgsmål vedr tag på StackOverflow (udkom for nylig tag i den russiske version af SO) Og efter nøgleord på brødrister. der er Russisk chat i Gitter.

Vi opdaterer hver måned vurdering af open source-biblioteker til GUI-testning. Med hensyn til antallet af stjerner på github, kun Autohotkey (de har et meget stort fællesskab og en lang historie) og PyAutoGUI (hovedsagelig på grund af populariteten af ​​bøger af dens forfatter Al Sweigart: "Automatér de kedelige ting med Python" og andre) vokser hurtigere.

Kilde: www.habr.com

Tilføj en kommentar