Bibliothèque Python — est un projet open source d'automatisation des applications GUI de bureau sur Windows. Au cours des deux dernières années, de nouvelles fonctionnalités majeures ont été ajoutées :
- Support de la technologie MS UI Automation. L'interface reste la même, mais elle prend désormais en charge : WinForms, WPF, Qt5, Windows Store (UWP), et bien plus encore — presque tout ce qui existe sur Windows.
- Système de backends/plugins (il y en a actuellement deux sous le capot : le défaillant
"win32"et le nouveau"uia"). Nous avançons progressivement vers la multiplateforme. - Hooks Win32 pour la souris et le clavier (touches rapides dans le style de pyHook).
Nous ferons également un bref aperçu de ce qui existe en open source pour l'automatisation de bureau (sans prétendre à une comparaison sérieuse).
Cet article est en partie une transcription d'une présentation lors de la conférence SQA Days 20 à Minsk ( et ), et en partie une version russe pour pywinauto.
- Approches principales
- Principales technologies d'accessibilité de bureau
Commençons par un bref aperçu de l'open source dans ce domaine. Pour les applications GUI de bureau, c'est un peu plus complexe que pour le web, qui dispose de Selenium. Voici les principales approches :
Méthode des coordonnées
Nous codons en dur les points de clics, espérant des réussites.
[+] Multiplateforme, facilement réalisable.
[+] Facile de faire un enregistrement « record-replay » des tests.
[-] Le plus instable face aux changements de résolution d'écran, de thèmes, de polices, de tailles de fenêtres, etc.
[-] Nécessite d'énormes efforts de maintenance, souvent plus simple de régénérer les tests à partir de zéro ou de tester manuellement.
[-] Automatise uniquement les actions, d'autres méthodes existent pour la vérification et l'extraction de données.
Outils (multiplateformes) : , , et bien d'autres. En général, des outils plus complexes incluent cette fonctionnalité (pas toujours de manière multiplateforme).
Il convient de noter que la méthode de coordonnées peut compléter d'autres approches. Par exemple, pour une graphique personnalisée, on peut cliquer sur des coordonnées relatives (à partir du coin supérieur gauche de la fenêtre/élément, et non de tout l'écran) — c'est généralement assez fiable, surtout si l'on prend en compte la longueur/la largeur de l'ensemble de l'élément (alors même une résolution d'écran différente ne pose pas de problème).
Une autre option : ne sélectionner qu'une seule machine avec des configurations stables pour les tests (non cross-plateforme, mais dans certains cas cela fonctionne).
Reconnaissance d'images de référence
[+] Cross-plateforme
[+-] Relativement fiable (mieux que la méthode de coordonnées), mais nécessite tout de même certaines astuces.
[-+] Relativement lent, car cela nécessite des ressources CPU pour les algorithmes de reconnaissance.
[-] En ce qui concerne la reconnaissance de texte (OCR), il ne s'agit généralement pas de la parole => impossible de récupérer les données textuelles. À ma connaissance, les solutions OCR existantes ne sont pas très fiables pour ce type de tâche et ne sont pas largement utilisées (bienvenue dans les commentaires si ce n'est plus le cas).
Outils : , (compatible Sikuli, en Python pur), .
Technologies d'accessibilité
[+] Méthode la plus fiable, car elle permet de rechercher par texte, indépendamment de la manière dont il est rendu par le système ou le framework.
[+] Permet d'extraire des données textuelles => facilite la vérification des résultats de tests.
[+] En général, la plus rapide, car elle consomme presque aucune ressource CPU.
[-] Difficile de créer un outil cross-plateforme : absolument toutes les bibliothèques open-source prennent en charge une ou deux technologies d'accessibilité. Personne ne prend en charge l'ensemble Windows/Linux/MacOS, à part des solutions payantes comme TestComplete, UFT ou Squish.
[-] Cette technologie n'est pas toujours disponible. Par exemple, tester un écran de démarrage dans VirtualBox — il est impossible de s'en passer sans reconnaissance d'image. Mais dans de nombreux cas classiques, l'approche d'accessibilité est tout de même applicable. C'est de cela dont il sera question ensuite.
Outils : en C#, en C# (compatible Selenium), en C# (compatible Appium), , (compatible avec LDTP), , en Ruby, (Linux Desktop Testing Project) et sa version Windows .
LDTP est sans doute le seul outil open-source cross-plateforme (ou plutôt un ensemble de bibliothèques) basé sur des technologies d'accessibilité. Cependant, il n'est pas très populaire. Je ne l'ai pas utilisé, mais d'après les avis, son interface n'est pas la plus conviviale. Si vous avez des commentaires positifs, merci de les partager.
Backdoor de test (a.k.a. vélo interne)
Pour les applications multiplateformes, les développeurs créent souvent un mécanisme interne pour garantir la testabilité. Par exemple, ils créent un serveur TCP de service dans l'application, auquel les tests se connectent et envoient des commandes textuelles : sur quoi cliquer, d'où obtenir des données, etc. Sûr, mais pas universel.
Principales technologies d'accessibilité de bureau
Le bon vieux Win32 API
La plupart des applications Windows, écrites avant la sortie de WPF et ensuite du Windows Store, sont d'une manière ou d'une autre basées sur l'API Win32. En particulier, MFC, WTL, C++ Builder, Delphi, VB6 — tous ces outils utilisent l'API Win32. Même Windows Forms — largement compatible avec l'API Win32.
Outils : (similaire à VB) et enveloppe Python , (langage propriétaire, dispose d'une interface IDispatch COM), (Python), (Ruby), (Ruby).
Microsoft UI Automation
Le principal avantage : la technologie MS UI Automation prend en charge la majorité écrasante des applications GUI sur Windows avec quelques exceptions. Problème : elle n'est pas beaucoup plus facile à apprendre que l'API Win32. Sinon, personne ne créerait d'enveloppes autour d'elle.
En réalité, c'est un ensemble d'interfaces COM personnalisées (principalement, UIAutomationCore.dll), et elle possède également un wrapper .NET sous la forme de namespace System.Windows.Automation. Elle a d'ailleurs un bug introduit, en raison duquel certains éléments d'interface utilisateur peuvent être omis. Il vaut donc mieux utiliser UIAutomationCore.dll directement (si vous avez entendu parler de UiaComWrapper en C#, c'est ça).
Variantes des interfaces COM :
(1) IUknown de base — « la racine de tous les maux ». Le plus low-level, pas du tout convivial.
(2) IDispatch et dérivées (par exemple, Excel.Application), qui peuvent être utilisées en Python à l'aide du paquet win32com.client (inclus dans pyWin32). La variante la plus pratique et élégante.
(3) Interfaces personnalisées, avec lesquelles un paquet Python tiers sait travailler .
Outils : en C#, 0.6.0+, en C#, (leur code source des enveloppes C sur UIAutomationCore.dll n'est pas divulgué), en Ruby.
AT-SPI
Bien que presque toutes les distributions Linux soient basées sur le système X Window (dans Fedora 25, les « X » ont été remplacés par Wayland), les « X » ne permettent d'interagir qu'avec les fenêtres de niveau supérieur et la souris/le clavier. Pour une analyse détaillée des boutons, des listes et ainsi de suite — il existe la technologie AT-SPI. Les gestionnaires de fenêtres les plus populaires possèdent un démon AT-SPI registry, qui fournit pour les applications un GUI automatisable (au moins Qt et GTK sont pris en charge).
Outils : .
pyatspi2, à mon avis, contient trop de dépendances comme PyGObject. La technologie elle-même est disponible sous la forme d'une bibliothèque dynamique ordinaire libatspi.so. Elle est accessible par . Pour la bibliothèque pywinauto, nous prévoyons d'implémenter le support d'AT-SPI de cette manière : en chargeant libatspi.so et le module ctypes. Il y a un petit problème concernant l'utilisation de la bonne version, car pour les applications GTK+ et Qt, elles sont légèrement différentes. On peut s'attendre à une sortie probable de pywinauto 0.7.0 avec un support complet de Linux dans la première moitié de 2018.
Apple Accessibility API
Sur MacOS, il existe son propre langage d'automatisation, AppleScript. Pour réaliser quelque chose de similaire en Python, il est bien sûr nécessaire d'utiliser des fonctions d'ObjectiveC. À partir de MacOS 10.6, il semble que le paquet pyobjc soit inclus dans Python préinstallé. Cela facilitera également la liste des dépendances pour un support futur dans pywinauto.
Outils : En plus du langage AppleScript, il convient de prêter attention à , aussi connu sous le nom de pyatom. Il est compatible en interface avec LDTP, mais c'est aussi une bibliothèque autonome. Il y a un , écrit par un de mes étudiants. Il y a un problème connu : les timings flexibles ne fonctionnent pas (méthodes waitFor*). Mais, dans l'ensemble, c'est une bonne chose.
Comment commencer à travailler avec pywinauto
La première chose à faire est de se munir d'un inspecteur d'objets GUI (ce qu'on appelle un outil Spy). Cela aidera à examiner l'application de l'intérieur : comment la hiérarchie des éléments est structurée, quels attributs sont disponibles. Les inspecteurs d'objets les plus connus sont :
- Spy++ — inclus avec Visual Studio, y compris les éditions Express ou Community. Utilise l'API Win32. Son clone connu est AutoIt Window Info.
- Inspect.exe — inclus dans le Windows SDK. S'il est installé, vous pouvez le trouver dans le dossier
C:Program Files (x86)Windows Kitsbinx64. Dans l'inspecteur lui-même, il faut choisir le mode UI Automation au lieu de MS AA (Active Accessibility, ancêtre de l'UI Automation).
Après avoir inspecté l'application, nous choisissons le backend que nous allons utiliser. Il suffit de spécifier le nom du backend lors de la création de l'objet Application.
- backend=»win32″ — est utilisé par défaut, fonctionne bien avec MFC, WTL, VB6 et d'autres applications anciennes.
- backend=»uia» — nouveau backend pour MS UI Automation : fonctionne parfaitement avec WPF et WinForms ; également bon pour les applications Delphi et Windows Store ; fonctionne avec Qt5 et certaines applications Java. En général, si Inspect.exe voit les éléments et leurs attributs, cela signifie que ce backend est approprié. En fait, la plupart des navigateurs supportent également l'UI Automation (Mozilla par défaut, et pour Chrome, il faut passer le paramètre de ligne de commande
--force-renderer-accessibility, pour voir les éléments sur les pages dans Inspect.exe). Bien sûr, la concurrence avec Selenium dans ce domaine est peu probable. C'est juste un autre moyen de travailler avec le navigateur (peut être utile pour un scénario interproduits).
Points d'entrée pour l'automatisation
L'application a été suffisamment étudiée. Il est temps de créer un objet Application et de le lancer ou de se connecter à une instance déjà en cours d'exécution. Ce n'est pas juste un clone d'une classe standard. subprocess.Popen, mais plutôt un objet d'introduction qui limite toutes vos actions aux frontières du processus. C'est très utile si plusieurs instances de l'application sont lancées et que vous n'avez pas envie d'interagir avec les autres.
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# Décrivons la fenêtre que nous voulons trouver dans le processus Notepad.exe
dlg_spec = app.UntitledNotepad
# attendons que la fenêtre apparaisse réellement
actionable_dlg = dlg_spec.wait('visible')Si vous souhaitez contrôler plusieurs applications en même temps, la classe Desktopvous sera utile. Par exemple, dans la calculatrice de Win10, la hiérarchie des éléments est étalée sur plusieurs processus (pas seulement calc.exe). Donc, sans un objet Desktop , il n'y a pas d'autre choix.
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')L'objet racine (Application ou Desktop) est le seul endroit où il faut spécifier le backend. Tout le reste s'intègre de manière transparente dans le concept « spécification -> wrapper », dont nous parlerons ensuite.
Spécifications des fenêtres/éléments
C'est le concept principal sur lequel l'interface pywinauto est construite. Vous pouvez décrire une fenêtre / un élément de manière approximative ou plus détaillée, même s'il n'existe pas encore ou s'il est déjà fermé. La spécification de la fenêtre (objet WindowSpecification) conserve les critères à partir desquels il faut rechercher la véritable fenêtre ou l'élément.
Exemple de spécification de fenêtre détaillée :
>>> dlg_spec = app.window(title='Untitled - Notepad')
>>> dlg_spec
>>> dlg_spec.wrapper_object()La recherche de la fenêtre s'effectue en appelant la méthode .wrapper_object(). Elle renvoie un certain « wrapper » pour la véritable fenêtre / élément ou lance une ElementNotFoundError (parfois ElementAmbiguousError, si plusieurs éléments sont trouvés, alors il est nécessaire de préciser le critère de recherche). Ce « wrapper » sait déjà effectuer certaines actions avec l'élément ou en obtenir des données.
Python peut masquer l'appel .wrapper_object(), de sorte que le code final devient plus court. Nous recommandons de l'utiliser uniquement pour le débogage. Les deux lignes suivantes font exactement la même chose :
dlg_spec.wrapper_object().minimize() # débogage
dlg_spec.minimize() # productionIl existe de nombreux critères de recherche pour la spécification de fenêtre. Voici quelques exemples :
# могут иметь несколько уровней
app.window(title_re='.* - Notepad$').window(class_name='Edit')
# можно комбинировать критерии (как AND) и не ограничиваться одним процессом приложения
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')La liste de tous les critères possibles se trouve dans la documentation de la fonction .
Magie de l'accès par attribut et par clé
Python simplifie la création de spécifications de fenêtre et reconnaît les attributs des objets de manière dynamique (méthode redéfinie à l'intérieur __getattribute__). Bien sûr, le nom de l'attribut est soumis aux mêmes restrictions que le nom de toute variable (il est interdit d'insérer des espaces, des virgules et d'autres caractères spéciaux). Heureusement, pywinauto utilise un algorithme de recherche dit « meilleur match », qui est résistant aux fautes de frappe et aux petites variations.
app.UntitledNotepad
# c'est la même chose que
app.window(best_match='UntitledNotepad')Si des chaînes Unicode sont nécessaires (par exemple, pour le russe), des espaces, etc., vous pouvez accéder par clé (comme s'il s'agissait d'un dictionnaire ordinaire) :
app['Untitled - Notepad']
# c'est la même chose que
app.window(best_match='Untitled - Notepad')Cinq règles pour les noms magiques
Comment connaître les noms magiques standards ? Ce sont ceux qui sont attribués à l'élément avant la recherche. Si vous avez spécifié un nom suffisamment proche de l’original, alors l’élément sera trouvé.
- Par le titre (texte, nom) :
app.Properties.OK.click() - Par le texte et le type d’élément :
app.Properties.OKButton.click() - Par le type et le numéro :
app.Properties.Button3.click()(les nomsButton0etButton1sont attachés au premier élément trouvé,Button2— au deuxième, et ainsi de suite — c’est devenu historique) - Par le texte statique (à gauche ou en haut) et par le type :
app.OpenDialog.FileNameEdit.set_text("")(utile pour les éléments avec du texte dynamique) - Par le type et le texte à l'intérieur :
app.Properties.TabControlSharing.select("General")
En général, deux ou trois règles sont appliquées simultanément, rarement plus. Pour vérifier quels noms sont disponibles pour chaque élément, vous pouvez utiliser la méthode print_control_identifiers(). Elle peut imprimer l'arborescence des éléments à l'écran ou dans un fichier. Pour chaque élément, ses noms magiques standards sont imprimés. Vous pouvez également copier-coller à partir de là des spécifications plus détaillées des éléments enfants. Le résultat dans le script ressemblera à ceci :
app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")L'arborescence des éléments elle-même est généralement assez longue.
>>> app.Properties.print_control_identifiers()
Identifiants de contrôle :
Dialogue - 'Propriétés de Windows NT' (L688, T518, R1065, B1006)
[u'Propriétés de Windows NTDialogue', u'Dialogue', u'Propriétés de Windows NT']
child_window(title="Propriétés de Windows NT", 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")
|
| Édition - 'Nom du dossier :' (L790, T596, R1036, B619)
| [u'3', 'Édition', u'Edit1', u'Edit0']
| child_window(title="Nom du dossier :", auto_id="13156", control_type="Edit")
|
| Statique - 'Type :' (L717, T643, R780, B658)
| [u'Type:Statique', u'Statique', u'Statique1', u'Statique0', u'Type:']
| child_window(title="Type:", auto_id="13080", control_type="Text")
|
| Édition - 'Type :' (L790, T643, R1036, B666)
| [u'4', 'Edit2', u'Type:Edit']
| child_window(title="Type:", auto_id="13059", control_type="Edit")
|
| Statique - 'Emplacement :' (L717, T669, R780, B684)
| [u'Emplacement:Statique', u'Emplacement:', u'Statique2']
| child_window(title="Emplacement:", auto_id="13089", control_type="Text")
|
| Édition - 'Emplacement :' (L790, T669, R1036, B692)
| ['Edit3', u'Emplacement:Édition', u'5']
| child_window(title="Emplacement:", auto_id="13065", control_type="Edit")
|
| Statique - 'Taille :' (L717, T695, R780, B710)
| [u'Taille:Statique', u'Taille:', u'Statique3']
| child_window(title="Taille:", auto_id="13081", control_type="Text")
|
| Édition - 'Taille :' (L790, T695, R1036, B718)
| ['Edit4', u'6', u'Taille:Édition']
| child_window(title="Taille:", auto_id="13064", control_type="Edit")
|
| Statique - 'Taille sur disque :' (L717, T721, R780, B736)
| [u'Taille sur disque:', u'Taille sur disque:Statique', u'Statique4']
| child_window(title="Taille sur disque:", auto_id="13107", control_type="Text")
|
| Édition - 'Taille sur disque :' (L790, T721, R1036, B744)
| ['Edit5', u'7', u'Taille sur disque:Édition']
| child_window(title="Taille sur disque:", auto_id="13106", control_type="Edit")
|
| Statique - 'Contient :' (L717, T747, R780, B762)
| [u'Contient:1', u'Contient:0', u'Contient:Statique', u'Statique5', u'Contient:']
| child_window(title="Contient:", auto_id="13088", control_type="Text")
|
| Édition - 'Contient :' (L790, T747, R1036, B770)
| [u'8', 'Edit6', u'Contient:Édition']
| child_window(title="Contient:", auto_id="13087", control_type="Edit")
|
| Image - 'Contient :' (L717, T773, R1035, B775)
| [u'Contient:Image', 'Image3', u'Contient:2']
| child_window(title="Contient:", auto_id="13096", control_type="Image")
|
| Statique - 'Créé :' (L717, T786, R780, B801)
| [u'Créé:', u'Créé:Statique', u'Statique6', u'Créé:1', u'Créé:0']
| child_window(title="Créé:", auto_id="13092", control_type="Text")
|
| Édition - 'Créé :' (L790, T786, R1036, B809)
| [u'Créé:Édition', 'Edit7', u'9']
| child_window(title="Créé:", auto_id="13072", control_type="Edit")
|
| Image - 'Créé :' (L717, T812, R1035, B814)
| [u'Créé:Image', 'Image4', u'Créé:2']
| child_window(title="Créé:", auto_id="13097", control_type="Image")
|
| Statique - 'Attributs :' (L717, T825, R780, B840)
| [u'Attributs:Statique', u'Statique7', u'Attributs:']
| child_window(title="Attributs:", auto_id="13091", control_type="Text")
|
| Case à cocher - 'Lecture seule (s'applique uniquement aux fichiers dans le dossier)' (L790, T825, R1035, B841)
| [u'CheckBox0', u'CheckBox1', 'Case à cocher', u'Lecture seule (s'applique uniquement aux fichiers dans le dossier)Case à cocher', u'Lecture seule (s'applique uniquement aux fichiers dans le dossier)']
| child_window(title="Lecture seule (s'applique uniquement aux fichiers dans le dossier)", auto_id="13075", control_type="CheckBox")
|
| Case à cocher - 'Caché' (L790, T848, R865, B864)
| ['CheckBox2', u'Case à cocher Caché', u'Caché']
| child_window(title="Caché", auto_id="13076", control_type="CheckBox")
|
| Bouton - 'Avancé...' (L930, T845, R1035, B868)
| [u'Avancé...', u'Bouton Avancé...', 'Bouton', u'Bouton1', u'Bouton0']
| child_window(title="Avancé...", auto_id="13154", control_type="Button")
|
| Bouton - 'OK' (L814, T968, R889, B991)
| ['Bouton2', u'OK', u'Bouton OK']
| child_window(title="OK", auto_id="1", control_type="Button")
|
| Bouton - 'Annuler' (L895, T968, R970, B991)
| ['Bouton3', u'Bouton Annuler', u'Annuler']
| child_window(title="Annuler", auto_id="2", control_type="Button")
|
| Bouton - 'Appliquer' (L976, T968, R1051, B991)
| ['Bouton4', u'Bouton Appliquer', u'Appliquer']
| child_window(title="Appliquer", auto_id="12321", control_type="Button")
|
| Contrôle des onglets - '' (L702, T556, R1051, B962)
| [u'10', u'ContrôleOngletPartage', u'ContrôleOngletVersions Précédentes', u'ContrôleOngletSécurité', u'ContrôleOnglet', u'ContrôleOngletPersonnaliser']
| child_window(auto_id="12320", control_type="Tab")
| |
| | Éléments d'onglet - 'Général' (L704, T558, R753, B576)
| | [u'ÉlémentOngletGénéral', 'ÉlémentOnglet', u'Général', u'ÉlémentOnglet0', u'ÉlémentOnglet1']
| | child_window(title="Général", control_type="TabItem")
| |
| | Éléments d'onglet - 'Partage' (L753, T558, R801, B576)
| | [u'Partage', u'ÉlémentOngletPartage', 'ÉlémentOnglet2']
| | child_window(title="Partage", control_type="TabItem")
| |
| | Éléments d'onglet - 'Sécurité' (L801, T558, R851, B576)
| | [u'Sécurité', 'ÉlémentOnglet3', u'ÉlémentOngletSécurité']
| | child_window(title="Sécurité", control_type="TabItem")
| |
| | Éléments d'onglet - 'Versions Précédentes' (L851, T558, R947, B576)
| | [u'ÉlémentOngletVersions Précédentes', u'Versions Précédentes', 'ÉlémentOnglet4']
| | child_window(title="Versions Précédentes", control_type="TabItem")
| |
| | Éléments d'onglet - 'Personnaliser' (L947, T558, R1007, B576)
| | [u'ÉlémentOngletPersonnaliser', 'ÉlémentOnglet5', u'Personnaliser']
| | child_window(title="Personnaliser", control_type="TabItem")
|
| Barre de titre - 'Aucun' (L712, T521, R1057, B549)
| ['BarreDeTitre', u'11']
| |
| | Menu - 'Système' (L696, T526, R718, B548)
| | [u'Système0', u'Système', u'Système1', u'Menu', u'MenuSystème']
| | child_window(title="Système", auto_id="MenuBar", control_type="MenuBar")
| | |
| | | Élément de menu - 'Système' (L696, T526, R718, B548)
| | | [u'Système2', u'ÉlémentDeMenu', u'ÉlémentMenuSystème']
| | | child_window(title="Système", control_type="MenuItem")
| |
| | Bouton - 'Fermer' (L1024, T519, R1058, B549)
| | [u'BoutonFermer', u'Fermer', 'Bouton5']
| | child_window(title="Fermer", control_type="Button")Dans certains cas, l'impression de tout l'arbre peut ralentir (par exemple, dans iTunes, il y a jusqu'à trois mille éléments sur un seul onglet !), mais on peut utiliser le paramètre depth (profondeur) : depth=1 — l'élément lui-même, depth=2 — uniquement les enfants directs, et ainsi de suite. On peut également le spécifier dans les spécifications lors de la création de child_window.
Exemples
Nous mettons constamment à jour . Parmi les récents, on peut noter l'automatisation de l'analyseur réseau WireShark (c'est un bon exemple d'application Qt5 ; bien que cette tâche puisse être réalisée sans interface graphique, car il y a scapy.Sniffer du paquet Python ). Il y a aussi un exemple d'automatisation de MS Paint avec sa barre d'outils Ribbon.
Un autre excellent exemple, réalisé par mon étudiant : (ce dernier sera transféré dans le référentiel principal un peu plus tard).
Et, bien sûr, un exemple d'abonnement aux événements du clavier (touche de raccourci) et de la souris :
.
Remerciements
Un grand merci à ceux qui aident constamment à développer le projet. Pour moi et c'est un hobby permanent. Deux de mes étudiants de NNGU ont récemment obtenu leur diplôme de baccalauréat sur ce sujet. a grandement contribué au support de MS UI Automation et a récemment commencé à développer un générateur de code automatique selon le principe «enregistrement-reproduction» basé sur les propriétés textuelles (c'est la fonctionnalité la plus complexe), pour l'instant seulement pour le backend «uia». développe un nouveau backend sous Linux basé sur AT-SPI (modules mouse et keyboard basé sur — déjà dans les versions 0.6.x).
Comme j'enseigne depuis un certain temps un cours spécial sur l'automatisation avec Python, certains étudiants de master réalisent des devoirs en mettant en œuvre de petites fonctionnalités ou des exemples d'automatisation. Certaines choses clés en phase de recherche ont également été découvertes par des étudiants. Bien que parfois, il faille veiller à la qualité du code. Cela est fortement aidé par des analyseurs statiques (QuantifiedCode, Codacy et Landscape) et des tests automatiques dans le cloud (service AppVeyor) avec une couverture de code d'environ 95%.
Merci aussi à tous ceux qui laissent des commentaires, ouvrent des bogues et envoient des demandes de tirage !
Ressources supplémentaires
Nous suivons les questions par (un nouveau tag a récemment été ajouté Deep Speech . Il y a .
Nous mettons à jour chaque mois En termes de popularité sur GitHub, seuls Autohotkey (qui bénéficie d'une très grande communauté et d'une longue histoire) et PyAutoGUI (en grande partie grâce à la popularité des livres de son auteur Al Sweigart : « Automate the Boring Stuff with Python » et d'autres) se développent plus rapidement.
Source : habr.com
