Automatisierung von Desktop-GUIs mit Python + pywinauto: Eine Einführung in MS UI Automation

Python-Bibliothek pywinauto — ist ein Open-Source-Projekt zur Automatisierung von Desktop-GUI-Anwendungen unter Windows. In den letzten zwei Jahren wurden bedeutende neue Funktionen hinzugefügt:

  • Unterstützung für die MS UI Automation-Technologie. Die Benutzerschnittstelle bleibt unverändert, jetzt werden auch unterstützt: WinForms, WPF, Qt5, Windows Store (UWP) und weitere — fast alles, was unter Windows verfügbar ist.
  • Ein System von Backends/Plugins (derzeit sind zwei implementiert: das Standard- "win32" und das neue "uia"). Der nächste Schritt ist die Hinwendung zur Plattformunabhängigkeit.
  • Win32-Hooks für Maus und Tastatur (Hotkeys im Stil von pyHook).

Außerdem werden wir einen kurzen Überblick darüber geben, was im Open Source-Bereich für die Desktop-Automatisierung verfügbar ist (ohne den Anspruch auf ernsthafte Vergleiche).

Dieser Artikel ist teilweise eine Transkription eines Vortrags von der SQA Days 20 Konferenz in Minsk (die Aufzeichnung und Folien), teilweise die deutsche Version des Getting Started Guide für pywinauto.

Lassen Sie uns mit einem kurzen Überblick über Open Source in diesem Bereich beginnen. Für Desktop-GUI-Anwendungen ist es etwas komplizierter als für das Web, das mit Selenium arbeitet. Hier sind die Hauptansätze:

Koordinatenmethode

Hardcoded Klickpunkte, in der Hoffnung auf glückliche Treffer.
[+] Plattformübergreifend, leicht umsetzbar.
[+] Einfaches Erstellen von 'Record-Replay' Testaufzeichnungen.
[-] Am instabilsten bei Bildschirmauflösungen, Themen, Schriftarten, Fenstergrößen usw.
[-] Erfordert enorme Anstrengungen für die Wartung, oft ist es einfacher, Tests von Grund auf neu zu generieren oder manuell zu testen.
[-] Automatisiert nur Aktionen; es gibt andere Methoden zur Verifizierung und zur Datenerfassung.

Werkzeuge (plattformübergreifend): autopy, PyAutoGUI, PyUserInput und viele andere. In der Regel enthalten komplexere Werkzeuge diese Funktionalität (nicht immer plattformübergreifend).

Es ist erwähnenswert, dass die Koordinatenmethode andere Ansätze ergänzen kann. Zum Beispiel kann man für benutzerdefinierte Grafiken relative Koordinaten (vom oberen linken Fenster-/Elementrand und nicht vom gesamten Bildschirm aus) verwenden – in der Regel ist dies ziemlich zuverlässig, insbesondere wenn man die Länge/Breite des gesamten Elements berücksichtigt (dann stört eine unterschiedliche Bildschirmauflösung nicht).

Eine andere Möglichkeit besteht darin, nur eine Maschine mit stabilen Einstellungen für die Tests auszuwählen (nicht plattformübergreifend, aber in einigen Fällen geeignet).

Erkennung von Referenzbildern

[+] Plattformübergreifend
[+-] Relativ zuverlässig (besser als die Koordinatenmethode), erfordert jedoch einige Kniffe.
[-+] Relativ langsam, da es CPU-Ressourcen für die Erkennungsalgorithmen benötigt.
[-] Über Texterkennung (OCR) wird in der Regel nicht gesprochen => man kann keine Textdaten extrahieren. Soweit ich weiß, sind bestehende OCR-Lösungen für diese Art von Aufgaben nicht besonders zuverlässig und haben keine breite Anwendung (Kommentare willkommen, falls das mittlerweile anders ist).

Werkzeuge: Sikuli, Lackey (Sikuli-kompatibel, auf purem Python) PyAutoGUI.

Zugänglichkeitstechnologien.

[+] Die zuverlässigste Methode, da sie eine Suche nach Text ermöglicht, unabhängig davon, wie dieser von System oder Framework gerendert wird.
[+] Ermöglicht das Extrahieren von Textdaten => erleichtert die Verifizierung von Testergebnissen.
[+] In der Regel der schnellste, da er nahezu keine CPU-Ressourcen verbraucht.
[-] Es ist schwierig, ein plattformübergreifendes Tool zu erstellen: Absolut alle Open-Source-Bibliotheken unterstützen ein oder zwei Accessibility-Technologien. Windows/Linux/MacOS werden nicht vollständig von jemandem unterstützt, außer von kostenpflichtigen wie TestComplete, UFT oder Squish.
[-] Solche Technologien sind nicht immer grundsätzlich verfügbar. Zum Beispiel ist das Testen eines Bootbildschirms in VirtualBox ohne Bildverarbeitung nicht möglich. In vielen klassischen Fällen ist der Accessibility-Ansatz jedoch anwendbar. Darauf wird hier weiter eingegangen.

Werkzeuge: TestStack.White in C#, Winium.Desktop in C# (Selenium-kompatibel), MS WinAppDriver in C# (Appium-kompatibel), pywinauto, pyatom (kompatibel mit LDTP), Python-UIAutomation-for-Windows, RAutomation in Ruby, LDTP (Linux Desktop Testing Project) und seine Windows-Version. Cobra.

LDTP ist möglicherweise das einzige plattformübergreifende Open-Source-Tool (genauer gesagt, eine Sammlung von Bibliotheken) basierend auf Accessibility-Technologien. Es ist jedoch nicht sehr populär. Ich habe es selbst nicht benutzt, aber den Rückmeldungen zufolge ist die Benutzeroberfläche nicht besonders benutzerfreundlich. Wenn es positive Rückmeldungen gibt, bitte in den Kommentaren teilen.

Test-Backdoor (auch bekannt als internes Werkzeug)

Für plattformübergreifende Anwendungen entwickeln die Entwickler häufig einen internen Mechanismus zur Gewährleistung der Testbarkeit. Beispielsweise wird ein Dienst-TCP-Server in der Anwendung erstellt, mit dem die Tests verbunden sind und textbasierte Befehle gesendet werden: worauf zu klicken ist, wo die Daten herkommen usw. Zuverlässig, aber nicht universell.

Wesentliche Desktop-Zugänglichkeitstechnologien

Der altehrwürdige Win32 API

Die meisten Windows-Anwendungen, die vor der Einführung von WPF und anschließend dem Windows Store entwickelt wurden, basieren mehr oder weniger auf der Win32-API. Insbesondere MFC, WTL, C++ Builder, Delphi, VB6 – all diese Tools verwenden die Win32-API. Sogar Windows Forms sind zu einem großen Teil mit der Win32-API kompatibel.

Werkzeuge: AutoIt (ähnelt VB) und eine Python-Bindung pyautoit, AutoHotkey (eigene Sprache, hat ein IDispatch COM-Interface), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).

Microsoft UI Automation

Der Hauptvorteil: Die MS UI Automation-Technologie unterstützt die überwiegende Mehrheit der GUI-Anwendungen unter Windows mit wenigen Ausnahmen. Das Problem: Sie ist nicht viel einfacher zu erlernen als die Win32-API. Andernfalls würde niemand Wrapper darüber erstellen.

Tatsächlich handelt es sich um eine Reihe von benutzerdefinierten COM-Interfaces (hauptsächlich, UIAutomationCore.dll), und hat außerdem eine .NET-Hülle in Form von namespace System.Windows.Automation. Übrigens gibt es einen eingeführten Bug, der dazu führt, dass einige UI-Elemente übersehen werden können. Daher ist es besser, UIAutomationCore.dll direkt zu verwenden (wenn Sie von UiaComWrapper in C# gehört haben, ist das es).

Varianten von COM-Schnittstellen:

(1) Basis IUknown — „der Ursprung allen Übels“. Die grundlegendste, keineswegs benutzerfreundliche Schnittstelle.
(2) IDispatch und abgeleitete Schnittstellen (zum Beispiel, Excel.Application), die in Python mithilfe des Pakets win32com.client (Teil von pyWin32) verwendet werden können. Die benutzerfreundlichste und eleganteste Option.
(3) Benutzerdefinierte Schnittstellen, mit denen das dritte Python-Paket comtypes.

Werkzeuge: TestStack.White in C#, pywinauto 0.6.0+, Winium.Desktop in C#, Python-UIAutomation-for-Windows arbeiten kann (deren Quellcode für die C-Wrapper um UIAutomationCore.dll nicht offengelegt ist), RAutomation in Ruby.

AT-SPI

Obwohl fast alle Distributionen der Linux-Familie auf dem X Window System basieren (in Fedora 25 wurden die „X“ durch Wayland ersetzt), ermöglichen die „X“ nur die Interaktion mit oberen Fenstern sowie Maus/Tastatur. Für eine detaillierte Analyse von Tasten, Listenfeldern usw. gibt es die Technologie AT-SPI. Die bekanntesten Fenstermanager verfügen über einen sogenannten AT-SPI-Registry-Daemon, der den Anwendungen eine automatisierbare GUI bietet (mindestens werden Qt und GTK unterstützt).

Werkzeuge: pyatspi2.

Meiner Meinung nach enthält pyatspi2 zu viele Abhängigkeiten vom gleichen Typ wie PyGObject. Die Technologie selbst ist als gewöhnliche dynamische Bibliothek verfügbar. libatspi.so. Sie ist verfügbar unter Referenzhandbuch. Für die Bibliothek pywinauto planen wir die Unterstützung von AT-SPI folgendermaßen zu implementieren: durch das Laden von libatspi.so und dem ctypes-Modul. Es gibt nur ein kleines Problem bei der Verwendung der benötigten Version, da diese für GTK+- und Qt-Anwendungen leicht unterschiedlich sind. Wahrscheinlich wird die Veröffentlichung von pywinauto 0.7.0 mit vollständiger Unterstützung für Linux in der ersten Hälfte des Jahres 2018 erwartet.

Apple Accessibility API

Unter MacOS gibt es eine eigene Automatisierungssprache, AppleScript. Um etwas Ähnliches in Python zu realisieren, müssen natürlich Funktionen aus ObjectiveC verwendet werden. Bereits seit MacOS 10.6 ist das pyobjc-Paket im vorinstallierten Python enthalten. Dies wird auch die Liste der Abhängigkeiten für die zukünftige Unterstützung in pywinauto erleichtern.

Werkzeuge: Neben der AppleScript-Sprache sollte man auch auf ATOMac, auch bekannt als pyatom, achten. Es ist in seiner Schnittstelle mit LDTP kompatibel, stellt aber ebenfalls eine eigenständige Bibliothek dar. Darauf gibt es ein Beispiel für die Automatisierung von iTunes unter macOS, das von meinem Studenten geschrieben wurde. Es gibt ein bekanntes Problem: flexible Timings funktionieren nicht (Methoden waitFor*). Aber insgesamt ist es eine brauchbare Lösung.

Wie man mit pywinauto arbeitet

Zunächst sollten Sie sich mit einem GUI-Inspektor für Objekte ausstatten (das, was man als Spy-Tool bezeichnet). Er hilft dabei, die Anwendung von innen zu erkunden: wie die Hierarchie der Elemente aufgebaut ist und welche Eigenschaften verfügbar sind. Die bekanntesten Objektinspektoren sind:

  • Spy++ — ist in Visual Studio enthalten, einschließlich Express oder Community Edition. Es verwendet die Win32 API. Auch bekannt ist sein Klon AutoIt Window Info.
  • Inspect.exe — ist im Windows SDK enthalten. Wenn es installiert ist, können Sie es unter 64-Bit-Windows im Ordner finden C:Program Files (x86)Windows Kitsbinx64. Im Inspektor müssen Sie den Modus auswählen UI Automation anstatt MS AA (Active Accessibility, Vorläufer von UI Automation).

Nachdem die Anwendung durchsichtig gemacht wurde, wählen wir das Backend aus, das wir verwenden möchten. Es reicht aus, den Namen des Backends beim Erstellen des Application-Objekts anzugeben.

  • backend=»win32″ — wird derzeit standardmäßig verwendet und funktioniert gut mit MFC, WTL, VB6 und anderen Legacy-Anwendungen.
  • backend=»uia» — ein neuer Backend für MS UI Automation: funktioniert perfekt mit WPF und WinForms; ebenfalls gut für Delphi und Windows Store-Anwendungen; arbeitet mit Qt5 und einigen Java-Anwendungen. Wenn Inspect.exe Elemente und deren Eigenschaften sieht, ist dieser Backend geeignet. Im Allgemeinen unterstützen die meisten Browser auch die UI Automation (Mozilla standardmäßig, für Chrome muss beim Start ein Befehlszeilenparameter übergeben werden). --force-renderer-accessibility, um Elemente auf den Seiten in Inspect.exe zu sehen). Natürlich ist die Konkurrenz mit Selenium in diesem Bereich kaum möglich. Es ist einfach ein weiterer Weg, um mit dem Browser zu arbeiten (nützlich für plattformübergreifende Szenarien).

Einstiegspunkte für die Automatisierung

Die Anwendung ist ausreichend dokumentiert. Es ist Zeit, ein Application-Objekt zu erstellen und es zu starten oder sich mit einem bereits laufenden zu verbinden. Es handelt sich nicht einfach um eine Kopie der Standardklasse subprocess.Popen, sondern um ein spezifisches Objekt, das alle Ihre Aktionen innerhalb der Grenzen des Prozesses einschränkt. Dies ist sehr nützlich, wenn mehrere Instanzen der Anwendung ausgeführt werden und die anderen nicht berührt werden sollen.

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

# Beschreiben Sie das Fenster, das wir im Prozess Notepad.exe finden möchten
dlg_spec = app.UntitledNotepad
# warten, bis das Fenster tatsächlich erscheint
actionable_dlg = dlg_spec.wait('visible')

Wenn Sie mehrere Anwendungen gleichzeitig steuern möchten, hilft Ihnen die Klasse Desktop. Zum Beispiel ist in der Rechner-App auf Win10 die Hierarchie der Elemente über mehrere Prozesse verteilt (nicht nur calc.exe). Daher ist ein Objekt Desktop unverzichtbar.

from subprocess import Popen
from pywinauto import Desktop

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

Das Wurzelobjekt (Application oder Desktop) ist der einzige Ort, an dem der Backend angegeben werden muss. Alles andere fügt sich nahtlos in das Konzept „Spezifikation->Wrapper“ ein, das weiter unten erläutert wird.

Spezifikationen von Fenstern/Elementen

Dies ist das Hauptkonzept, auf dem die pywinauto-Schnittstelle basiert. Sie können ein Fenster oder ein Element grob oder detaillierter beschreiben, selbst wenn es noch nicht existiert oder bereits geschlossen ist. Die Fensterspezifikation (Objekt WindowSpecification) enthält die Kriterien, nach denen das tatsächliche Fenster oder Element gesucht werden muss.

Beispiel für eine detaillierte Fensterspezifikation:

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

>>> dlg_spec


>>> dlg_spec.wrapper_object()

Die Fensterfindung erfolgt durch den Aufruf der Methode .wrapper_object(). Diese gibt einen "Wrapper" für das reale Fenster/Element zurück oder wirft ElementNotFoundError (manchmal ElementAmbiguousError, wenn mehrere Elemente gefunden werden, was bedeutet, dass die Suchkriterien präzisiert werden müssen). Dieser "Wrapper" kann bereits einige Aktionen mit dem Element durchführen oder Informationen daraus abrufen.

Python kann den Aufruf verbergen .wrapper_object(), sodass der endgültige Code kürzer wird. Wir empfehlen, ihn nur zur Fehlersuche zu verwenden. Die folgenden beiden Zeilen tun absolut dasselbe:

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

Es gibt zahlreiche Suchkriterien für die Fensterspezifikation. Hier sind nur einige Beispiele:

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

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

Eine Liste aller möglichen Kriterien finden Sie in der Dokumentation der Funktion pywinauto.findwindows.find_elements(…).

Die Magie des Zugriffs über Attribute und Schlüssel

Python vereinfacht die Erstellung von Fensterspezifikationen und erkennt die Attribute von Objekten dynamisch (in der Methode wird __getattribute__ überschrieben)). Natürlich gelten für den Namen eines Attributs dieselben Beschränkungen wie für den Namen jeder Variablen (keine Leerzeichen, Kommas und andere Sonderzeichen). Glücklicherweise verwendet pywinauto den sogenannten „Best Match“-Algorithmus zur Suche, der unempfindlich gegenüber Tippfehlern und kleinen Variationen ist.

app.UntitledNotepad
# das Gleiche wie
app.window(best_match='UntitledNotepad')

Wenn dennoch Unicode-Strings (zum Beispiel für die russische Sprache) benötigt werden, können Sie den Zugriff über den Schlüssel (als ob es ein normales Wörterbuch wäre) vornehmen:

app['Untitled - Notepad']
# das Gleiche wie
app.window(best_match='Untitled - Notepad')

Fünf Regeln für magische Namen

Wie erfährt man, welche magischen Namen Referenz sind? Die, die einem Element vor der Suche zugewiesen werden. Wenn der angegebene Name dem Referenznamen ähnlich genug ist, wird das Element gefunden.

  1. Nach dem Titel (Text, Name): app.Properties.OK.click()
  2. Nach Text und Typ des Elements: app.Properties.OKButton.click()
  3. Nach Typ und Nummer: app.Properties.Button3.click() (Namen Button0 und Button1 sind an das erste gefundene Element gebunden, Button2 — an das zweite, und dann weiter der Reihe nach — so hat sich das historisch ergeben)
  4. Nach statischem Text (links oder oben) und nach Typ: app.OpenDialog.FileNameEdit.set_text("") (nützlich für Elemente mit dynamischem Text)
  5. Nach Typ und Textinhalt: app.Properties.TabControlSharing.select("General")

In der Regel werden zwei bis drei Regeln gleichzeitig angewendet, selten mehr. Um zu überprüfen, welche spezifischen Namen für jedes Element verfügbar sind, kann die Methode print_control_identifiers()verwendet werden. Sie kann die Struktur der Elemente sowohl auf dem Bildschirm als auch in eine Datei ausgeben. Für jedes Element werden seine Referenzmagischen Namen ausgedruckt. Zusätzlich können Sie ausführlichere Spezifikationen der Unterelemente kopieren. Das Ergebnis im Skript sieht dann so aus:

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

Die Struktur der Elemente selbst ist in der Regel ein recht umfangreicher Textblock.

> > > app.Properties.print_control_identifiers()

Steuerelement-Identifikatoren:

Dialog - 'Eigenschaften von Windows NT' (L688, T518, R1065, B1006)
[u'Eigenschaften von Windows NT Dialog', u'Dialog', u'Eigenschaften von Windows NT']
child_window(title="Eigenschaften von Windows NT", control_type="Fenster")
   |
   | Bild - '' (L717, T589, R749, B622)
   | [u'', u'0', u'Bild1', u'Bild0', 'Bild', u'1']
   | child_window(auto_id="13057", control_type="Bild")
   |
   | Bild - '' (L717, T630, R1035, B632)
   | ['Bild2', u'2']
   | child_window(auto_id="13095", control_type="Bild")
   |
   | Bearbeiten - 'Ordnername:' (L790, T596, R1036, B619)
   | [u'3', 'Bearbeiten', u'Bearbeiten1', u'Bearbeiten0']
   | child_window(title="Ordnername:", auto_id="13156", control_type="Bearbeiten")
   |
   | Statisches - 'Typ:' (L717, T643, R780, B658)
   | [u'Typ:Statisch', u'Statisch', u'Statisch1', u'Statisch0', u'Typ:']
   | child_window(title="Typ:", auto_id="13080", control_type="Text")
   |
   | Bearbeiten - 'Typ:' (L790, T643, R1036, B666)
   | [u'4', 'Bearbeiten2', u'Typ:Bearbeiten']
   | child_window(title="Typ:", auto_id="13059", control_type="Bearbeiten")
   |
   | Statisches - 'Standort:' (L717, T669, R780, B684)
   | [u'Standort:Statisch', u'Standort:', u'Statisch2']
   | child_window(title="Standort:", auto_id="13089", control_type="Text")
   |
   | Bearbeiten - 'Standort:' (L790, T669, R1036, B692)
   | ['Bearbeiten3', u'Standort:Bearbeiten', u'5']
   | child_window(title="Standort:", auto_id="13065", control_type="Bearbeiten")
   |
   | Statisches - 'Größe:' (L717, T695, R780, B710)
   | [u'Größe:Statisch', u'Größe:', u'Statisch3']
   | child_window(title="Größe:", auto_id="13081", control_type="Text")
   |
   | Bearbeiten - 'Größe:' (L790, T695, R1036, B718)
   | ['Bearbeiten4', u'6', u'Größe:Bearbeiten']
   | child_window(title="Größe:", auto_id="13064", control_type="Bearbeiten")
   |
   | Statisches - 'Größe auf Diskette:' (L717, T721, R780, B736)
   | [u'Größe auf Diskette:', u'Größe auf Diskette:Statisch', u'Statisch4']
   | child_window(title="Größe auf Diskette:", auto_id="13107", control_type="Text")
   |
   | Bearbeiten - 'Größe auf Diskette:' (L790, T721, R1036, B744)
   | ['Bearbeiten5', u'7', u'Größe auf Diskette:Bearbeiten']
   | child_window(title="Größe auf Diskette:", auto_id="13106", control_type="Bearbeiten")
   |
   | Statisches - 'Enthält:' (L717, T747, R780, B762)
   | [u'Enthält:1', u'Enthält:0', u'Enthält:Statisch', u'Statisch5', u'Enthält:']
   | child_window(title="Enthält:", auto_id="13088", control_type="Text")
   |
   | Bearbeiten - 'Enthält:' (L790, T747, R1036, B770)
   | [u'8', 'Bearbeiten6', u'Enthält:Bearbeiten']
   | child_window(title="Enthält:", auto_id="13087", control_type="Bearbeiten")
   |
   | Bild - 'Enthält:' (L717, T773, R1035, B775)
   | [u'Enthält:Bild', 'Bild3', u'Enthält:2']
   | child_window(title="Enthält:", auto_id="13096", control_type="Bild")
   |
   | Statisches - 'Erstellt:' (L717, T786, R780, B801)
   | [u'Erstellt:', u'Erstellt:Statisch', u'Statisch6', u'Erstellt:1', u'Erstellt:0']
   | child_window(title="Erstellt:", auto_id="13092", control_type="Text")
   |
   | Bearbeiten - 'Erstellt:' (L790, T786, R1036, B809)
   | [u'Erstellt:Bearbeiten', 'Bearbeiten7', u'9']
   | child_window(title="Erstellt:", auto_id="13072", control_type="Bearbeiten")
   |
   | Bild - 'Erstellt:' (L717, T812, R1035, B814)
   | [u'Erstellt:Bild', 'Bild4', u'Erstellt:2']
   | child_window(title="Erstellt:", auto_id="13097", control_type="Bild")
   |
   | Statisches - 'Attribute:' (L717, T825, R780, B840)
   | [u'Attribute:Statisch', u'Statisch7', u'Attribute:']
   | child_window(title="Attribute:", auto_id="13091", control_type="Text")
   |
   | Kontrollkästchen - 'Schreibgeschützt (Gilt nur für Dateien im Ordner)' (L790, T825, R1035, B841)
   | [u'Kontrollkästchen0', u'Kontrollkästchen1', 'Kontrollkästchen', u'Schreibgeschützt (Gilt nur für Dateien im Ordner) Kontrollkästchen', u'Schreibgeschützt (Gilt nur für Dateien im Ordner)']
   | child_window(title="Schreibgeschützt (Gilt nur für Dateien im Ordner)", auto_id="13075", control_type="Kontrollkästchen")
   |
   | Kontrollkästchen - 'Versteckt' (L790, T848, R865, B864)
   | ['Kontrollkästchen2', u'VerstecktKontrollkästchen', u'Versteckt']
   | child_window(title="Versteckt", auto_id="13076", control_type="Kontrollkästchen")
   |
   | Button - 'Erweitert...' (L930, T845, R1035, B868)
   | [u'Erweitert...', u'Erweitert...Button', 'Button', u'Button1', u'Button0']
   | child_window(title="Erweitert...", auto_id="13154", control_type="Button")
   |
   | Button - 'OK' (L814, T968, R889, B991)
   | ['Button2', u'OK', u'OKButton']
   | child_window(title="OK", auto_id="1", control_type="Button")
   |
   | Button - 'Abbrechen' (L895, T968, R970, B991)
   | ['Button3', u'AbbrechenButton', u'Abbrechen']
   | child_window(title="Abbrechen", auto_id="2", control_type="Button")
   |
   | Button - 'Übernehmen' (L976, T968, R1051, B991)
   | ['Button4', u'ÜbernehmenButton', u'Übernehmen']
   | child_window(title="Übernehmen", auto_id="12321", control_type="Button")
   |
   | TabControl - '' (L702, T556, R1051, B962)
   | [u'10', u'TabControlTeilen', u'TabControlFrühere Versionen', u'TabControlSicherheit', u'TabControl', u'TabControlAnpassen']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'Allgemein' (L704, T558, R753, B576)
   |    | [u'AllgemeinTabItem', 'TabItem', u'Allgemein', u'TabItem0', u'TabItem1']
   |    | child_window(title="Allgemein", control_type="TabItem")
   |    |
   |    | TabItem - 'Freigabe' (L753, T558, R801, B576)
   |    | [u'Freigabe', u'FreigabeTabItem', 'TabItem2']
   |    | child_window(title="Freigabe", control_type="TabItem")
   |    |
   |    | TabItem - 'Sicherheit' (L801, T558, R851, B576)
   |    | [u'Sicherheit', 'TabItem3', u'SicherheitTabItem']
   |    | child_window(title="Sicherheit", control_type="TabItem")
   |    |
   |    | TabItem - 'Frühere Versionen' (L851, T558, R947, B576)
   |    | [u'Frühere VersionenTabItem', u'Frühere Versionen', 'TabItem4']
   |    | child_window(title="Frühere Versionen", control_type="TabItem")
   |    |
   |    | TabItem - 'Anpassen' (L947, T558, R1007, B576)
   |    | [u'AnpassenTabItem', 'TabItem5', u'Anpassen']
   |    | child_window(title="Anpassen", control_type="TabItem")
   |
   | Titelzeile - 'Keine' (L712, T521, R1057, B549)
   | ['Titelzeile', u'11']
   |    |
   |    | Menü - 'System' (L696, T526, R718, B548)
   |    | [u'System0', u'System', u'System1', u'Menü', u'SystemMenü']
   |    | child_window(title="System", auto_id="Menüleiste", control_type="Menüleiste")
   |    |    |
   |    |    | Menüartikel - 'System' (L696, T526, R718, B548)
   |    |    | [u'System2', u'Menüartikel', u'SystemMenüArtikel']
   |    |    | child_window(title="System", control_type="Menüartikel")
   |    |
   |    | Button - 'Schließen' (L1024, T519, R1058, B549)
   |    | [u'SchließenButton', u'Schließen', 'Button5']
   |    | child_window(title="Schließen", control_type="Button")

In einigen Fällen kann das Drucken des gesamten Baums langsam sein (zum Beispiel iTunes, wo auf einem Tab bis zu dreitausend Elemente sind!), aber es kann der Parameter depth (Tiefe): depth=1 — das aktuelle Element, depth=2 — nur die direkten Kinder, und so weiter. Dies kann auch in den Spezifikationen beim Erstellen angegeben werden child_window.

Beispiele

Wir erweitern ständig die Liste der Beispiele im Repository. Erwähnenswert ist die Automatisierung des Netzwerk-Analysetools WireShark (ein gutes Beispiel für eine Qt5-Anwendung; obwohl diese Aufgabe auch ohne GUI gelöst werden kann, da es scapy.Sniffer aus dem Python-Paket scapy). Ein weiteres Beispiel ist die Automatisierung von MS Paint mit seiner Ribbon-Toolbar.

Ein weiteres großartiges Beispiel, das mein Student geschrieben hat: das Ziehen einer Datei aus explorer.exe auf eine Chrome-Seite für Google Drive (es wird später im Haupt-Repository erscheinen).

Und natürlich ein Beispiel für das Abonnieren von Tastaturereignissen (Hotkeys) und Mäuseingaben:
hook_and_listen.py.

Danksagungen

Ein besonderer Dank gilt denjenigen, die ständig helfen, das Projekt weiterzuentwickeln. Für mich und Valentina ist es ein ständiges Hobby. Zwei meiner Studenten von der NNUN haben kürzlich ihre Bachelorarbeit zu diesem Thema erfolgreich verteidigt. Alexander hat einen großen Beitrag zur Unterstützung von MS UI Automation geleistet und hat kürzlich einen automatischen Code-Generator nach dem Prinzip "Aufzeichnen-Wiedergabe" basierend auf Texteigenschaften entwickelt (das ist das komplexeste Feature bisher), vorerst nur für das "uia"-Backend. Ivan entwickelt ein neues Backend unter Linux basierend auf AT-SPI (Module Maus und Tastatur auf Basis von python-xlib — bereits in den Releases 0.6.x).

Da ich schon seit einiger Zeit einen Spezialkurs zur Automatisierung mit Python unterrichte, erledigen einige Masterstudierende die Hausaufgaben, indem sie kleine Features oder Automatisierungsbeispiele umsetzen. Einige wichtige Dinge wurden in der Forschungsphase ebenfalls von den Studierenden entdeckt. Allerdings muss man manchmal streng auf die Codequalität achten. Hierbei helfen statische Analysatoren (QuantifiedCode, Codacy und Landscape) sowie automatische Tests in der Cloud (Dienst AppVeyor) mit einer Codeabdeckung von etwa 95%.

Auch ein Dankeschön an alle, die Feedback hinterlassen, Bugs melden und Pull-Requests senden!

Zusätzliche Ressourcen

Wir verfolgen die Fragen über das Tag auf StackOverflow (vor kurzem erschienen ein Tag in der russischen Version von SO) und zum Stichwort auf TosterAußerdem gibt es ein ein russischsprachiger Chat in Gitter.

Wir aktualisieren jeden Monat das Ranking der Open-Source-Bibliotheken für GUI-Tests. Nur Autohotkey (das über eine große Community und lange Geschichte verfügt) und PyAutoGUI (hauptsächlich aufgrund der Popularität der Bücher von Al Sweigart: „Automate the Boring Stuff with Python“ und anderen) wachsen schneller in Bezug auf die Anzahl der Sterne auf GitHub.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster