Automatizziamo l'interfaccia GUI desktop in Python + pywinauto: come interagire con l'MS UI Automation

Libreria Python pywinauto è un progetto open source per l'automazione delle applicazioni desktop GUI su Windows. Negli ultimi due anni sono state introdotte nuove funzionalità importanti:

  • Supporto per la tecnologia MS UI Automation. L'interfaccia rimane la stessa, ora supporta: WinForms, WPF, Qt5, Windows Store (UWP) e così via — praticamente tutto ciò che esiste su Windows.
  • Sistema di backend/plugin (attualmente ce ne sono due sotto il cofano: il predefinito "win32" e il nuovo "uia"). Procediamo quindi verso la cross-platform.
  • Hook Win32 per mouse e tastiera (hot keys nello stile di pyHook).

Realizzeremo anche una piccola panoramica di ciò che è disponibile in open source per l'automazione desktop (senza pretese di un serio confronto).

Questo articolo è in parte la trascrizione di una presentazione alla conferenza SQA Days 20 a Minsk (il video e diapositive), in parte la versione italiana Guida all'inizio per pywinauto.

Iniziamo con una breve panoramica del mondo open source in questo ambito. Per le applicazioni GUI desktop la situazione è un po' più complicata rispetto al web, che dispone di Selenium. Ecco i principali approcci:

Metodo delle coordinate

Hardcodiamo i punti di clic, sperando in colpi fortunati.
[+] Cross-platform, facilmente implementabile.
[+] Facile creare registrazioni "record-replay" per i test.
[-] Il più instabile in caso di modifiche alla risoluzione dello schermo, ai temi, ai font, alle dimensioni delle finestre, ecc.
[-] Richiede enormi sforzi di manutenzione, spesso è più semplice rigenerare i test da zero o testarli manualmente.
[-] Automatizza solo le azioni; ci sono altri metodi per la verifica e l'estrazione dei dati.

Strumenti (cross-platform): autopy, PyAutoGUI, PyUserInput e molti altri. In genere, strumenti più complessi includono questa funzionalità (non sempre in modo cross-platform).

Vale la pena dire che il metodo delle coordinate può integrare altri approcci. Ad esempio, per grafica personalizzata, è possibile cliccare su coordinate relative (dall'angolo in alto a sinistra della finestra/elemento, e non dell'intero schermo) — questo risulta generalmente abbastanza affidabile, specialmente se si considera la lunghezza/larghezza dell'intero elemento (così anche una risoluzione dello schermo diversa non costituisce un problema).

Un'altra opzione: selezionare solo una macchina con impostazioni stabili per i test (non cross-platform, ma in alcuni casi può funzionare).

Riconoscimento delle immagini di riferimento

[+] Cross-platform
[+-] Relativamente affidabile (meglio del metodo delle coordinate), ma richiede comunque qualche astuzia.
[-+] Relativamente lento, poiché richiede risorse CPU per gli algoritmi di riconoscimento.
[-] Riguardo al riconoscimento del testo (OCR), di solito non si parla = non è possibile estrarre dati testuali. Per quanto ne so, le soluzioni OCR esistenti non sono molto affidabili per questo tipo di compiti e non hanno un'applicazione diffusa (benvenuti nei commenti, se questo non è più così).

Strumenti: Sikuli, Lackey (compatibile con Sikuli, in puro Python), PyAutoGUI.

Tecnologie di accessibilità

[+] Il metodo più affidabile, poiché consente di cercare per testo, indipendentemente da come viene visualizzato dal sistema o dal framework.
[+] Consente di estrarre dati testuali => più facile verificare i risultati dei test.
[+] È generalmente il più veloce, poiché consuma quasi nessuna risorsa CPU.
[-] È difficile creare uno strumento multipiattaforma: tutte le librerie open-source supportano solo una o due tecnologie di accessibilità. Windows/Linux/MacOS non è supportato da nessuno, tranne che da soluzioni a pagamento come TestComplete, UFT o Squish.
[-] Non sempre tale tecnologia è disponibile. Ad esempio, il test della schermata di avvio all'interno di VirtualBox richiede il riconoscimento delle immagini. Tuttavia, in molti casi classici, l'approccio di accessibilità è comunque applicabile. Di questo si parlerà in seguito.

Strumenti: TestStack.White in C#, Winium.Desktop in C# (compatibile con Selenium), MS WinAppDriver in C# (compatibile con Appium), pywinauto, pyatom (compatibile con LDTP), Python-UIAutomation-for-Windows, RAutomation in Ruby, LDTP (Linux Desktop Testing Project) e la sua versione per Windows Cobra.

LDTP è probabilmente l'unico strumento open-source multipiattaforma (più precisamente, una famiglia di librerie) basato su tecnologie di accessibilità. Tuttavia, non è molto popolare. Non l'ho mai usato, ma secondo le recensioni, la sua interfaccia non è delle più comode. Se ci sono recensioni positive, vi prego di condividerle nei commenti.

Backdoor di prova (noto anche come bicicletta interna)

Per le applicazioni multipiattaforma, gli stessi sviluppatori spesso creano un meccanismo interno per garantire testabilità. Ad esempio, creano un server TCP di supporto nell'applicazione, a cui si collegano i test e inviano comandi testuali: su cosa cliccare, da dove prendere i dati, ecc. Affidabile, ma non universale.

Principali tecnologie di accessibilità desktop

Il buon vecchio Win32 API

La maggior parte delle applicazioni Windows scritte prima dell'arrivo di WPF e poi di Windows Store sono costruite in un modo o nell'altro sul Win32 API. In particolare, MFC, WTL, C++ Builder, Delphi, VB6: tutti questi strumenti utilizzano Win32 API. Anche Windows Forms è in larga misura compatibile con Win32 API.

Strumenti: AutoIt (simile a VB) e avvolgimento Python pyautoit, AutoHotkey (linguaggio proprietario, ha un'interfaccia IDispatch COM), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).

Microsoft UI Automation

Il grande vantaggio: la tecnologia MS UI Automation supporta la stragrande maggioranza delle applicazioni GUI su Windows con rare eccezioni. Problema: non è molto più facile da imparare rispetto al Win32 API. Altrimenti, nessuno creerebbe avvolgimenti su di essa.

In effetti, si tratta di un insieme di interfacce COM personalizzate (principalmente, UIAutomationCore.dll), e ha anche un involucro .NET sotto forma di namespace System.Windows.Automation. Ha, tra l'altro, presenta un bug introdotto che può causare la mancata visualizzazione di alcuni elementi dell'interfaccia utente. Perciò, è meglio utilizzare direttamente UIAutomationCore.dll (se avete sentito parlare di UiaComWrapper in C#, è proprio questo).

Tipi di interfacce COM:

(1) IUnknown di base — «la radice di tutti i mali». È il livello più basso, mai user-friendly.
(2) IDispatch e i suoi derivati (ad esempio, Excel.Application), che possono essere utilizzati in Python con il pacchetto win32com.client (incluso in pyWin32). La soluzione più comoda e gradevole.
(3) Interfacce personalizzate, con cui lavora il pacchetto Python di terze parti comtypes.

Strumenti: TestStack.White in C#, pywinauto 0.6.0+, Winium.Desktop in C#, Python-UIAutomation-for-Windows (il loro codice sorgente delle interfacce C sopra UIAutomationCore.dll non è rilasciato), RAutomation in Ruby.

AT-SPI

Nonostante quasi tutte le distribuzioni Linux siano basate su X Window System (in Fedora 25 «i numeri» sono stati sostituiti da Wayland), «i numeri» consentono di gestire solo finestre di livello superiore e mouse/tastiera. Per un'analisi dettagliata di pulsanti, caselle di elenco e così via — esiste la tecnologia AT-SPI. I più popolari gestori di finestre hanno un cosiddetto demone AT-SPI registry, che fornisce un'interfaccia grafica automatizzabile per le applicazioni (almeno supportano Qt e GTK).

Strumenti: pyatspi2.

Pyatspi2, a mio avviso, contiene troppe dipendenze simili a quelle di PyGObject. La tecnologia stessa è disponibile come una normale libreria dinamica. libatspi.so. A essa si accede Manuale di Riferimento. Per la libreria pywinauto stiamo pianificando di implementare il supporto AT-SPI proprio in questo modo: attraverso il caricamento di libatspi.so e il modulo ctypes. C'è solo un piccolo problema nell'uso della versione corretta, poiché per le applicazioni GTK+ e Qt sono leggermente diverse. È probabile che la versione 0.7.0 di pywinauto con pieno supporto Linux possa essere attesa nella prima metà del 2018.

Apple Accessibility API

Su MacOS c'è un proprio linguaggio di automazione chiamato AppleScript. Per implementare qualcosa di simile in Python, ovviamente, è necessario utilizzare funzioni in ObjectiveC. Da MacOS 10.6, sembra che il pacchetto pyobjc sia incluso nel Python preinstallato. Questo faciliterà anche l'elenco delle dipendenze per il futuro supporto in pywinauto.

Strumenti: oltre al linguaggio Apple Script, vale la pena prestare attenzione a ATOMac, noto anche come pyatom. È compatibile con l'interfaccia di LDTP, ma è anche una libreria autonoma. Ha un esempio di automazione di iTunes su macOS, scritto dal mio studente. C'è un problema noto: i tempi flessibili non funzionano (metodi waitFor*). Ma, nel complesso, è una buona cosa.

Come iniziare a lavorare con pywinauto

Per prima cosa, è importante avere a disposizione un ispettore GUI degli oggetti (noto come Spy tool). Questo strumento aiuta a esaminare l'applicazione dall'interno: come è organizzata la gerarchia degli elementi e quali proprietà sono disponibili. Gli ispettori di oggetti più conosciuti sono:

  • Spy++ — incluso in Visual Studio, comprese le versioni Express e Community Edition. Utilizza l'API Win32. È anche noto il suo clone AutoIt Window Info.
  • Inspect.exe — incluso nel Windows SDK. Se è installato, su Windows a 64 bit può essere trovato nella cartella C:Program Files (x86)Windows Kitsbinx64. Nell'ispettore stesso, è necessario selezionare la modalità UI Automation invece di MS AA (Active Accessibility, l'antenato di UI Automation).

Dopo aver esaminato a fondo l'applicazione, scegliamo il backend che utilizzeremo. È sufficiente specificare il nome del backend durante la creazione dell'oggetto Application.

  • backend="win32" — attualmente utilizzato per impostazione predefinita, funziona bene con MFC, WTL, VB6 e altre applicazioni legacy.
  • backend="uia" — un nuovo backend per MS UI Automation: funziona perfettamente con WPF e WinForms; è anche buono per applicazioni Delphi e Windows Store; compatibile con Qt5 e alcune applicazioni Java. In generale, se Inspect.exe vede elementi e le loro proprietà, significa che questo backend è appropriato. Fondamentalmente, la maggior parte dei browser supporta anche l'UI Automation (Mozilla per impostazione predefinita, mentre per Chrome è necessario fornire l'argomento della riga di comando all'avvio. --force-renderer-accessibility, per vedere gli elementi nelle pagine in Inspect.exe). Certamente, la competizione con Selenium in questo campo è difficile. È solo un altro modo per lavorare con il browser (può essere utile per scenari cross-prodotto).

Punti di accesso per l'automazione

L'applicazione è stata sufficientemente studiata. È tempo di creare un oggetto Application e avviarlo, o di unirsi a un'istanza già in esecuzione. Questo non è semplicemente un clone della classe standard subprocess.Popen, ma un oggetto introduttivo, che limita tutte le tue azioni entro i confini del processo. Questo è molto utile se sono in esecuzione più istanze dell'applicazione e non vuoi toccare le altre.

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

# Descriviamo la finestra che vogliamo trovare nel processo Notepad.exe
dlg_spec = app.UntitledNotepad
# aspettiamo finché la finestra non appare realmente
actionable_dlg = dlg_spec.wait('visible')

Se vuoi controllare più applicazioni contemporaneamente, puoi usare la classe Desktop. Ad esempio, nella calcolatrice di Win10, la gerarchia degli elementi è distribuita su più processi (non solo calc.exe). Quindi senza un oggetto Desktop non si può fare.

from subprocess import Popen
from pywinauto import Desktop

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

L'oggetto radice (Application o Desktop) è l'unico posto dove è necessario specificare il backend. Tutto il resto si integra nella concezione 'specifica->wrapper', di cui parleremo più avanti.

Specifiche delle finestre/elementi

Questa è la principale concezione su cui si basa l'interfaccia di pywinauto. Puoi descrivere la finestra/l'elemento in modo approssimativo o più dettagliato, anche se non esiste ancora o è già chiuso. La specifica della finestra (oggetto WindowSpecification) contiene i criteri secondo cui cercare la finestra o l'elemento reale.

Esempio di specifica finestra dettagliata:

> > > dlg_spec = app.window(title='Non assegnato - Blocco note')

> > > dlg_spec


> > > dlg_spec.wrapper_object()

La ricerca della finestra avviene chiamando il metodo .wrapper_object(). Restituisce un 'wrapper' per la finestra/reale elemento o genera ElementNotFoundError (a volte ElementAmbiguousError, se vengono trovati più elementi, il che significa che è necessario precisare i criteri di ricerca). Questo 'wrapper' è già in grado di eseguire alcune azioni sull'elemento o di recuperare dati da esso.

Python può nascondere la chiamata .wrapper_object(), quindi il codice finale diventa più conciso. Si consiglia di utilizzarlo solo per il debug. Le due righe seguenti fanno esattamente la stessa cosa:

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

Ci sono molti criteri di ricerca per la specifica della finestra. Ecco solo alcuni esempi:

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

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

L'elenco di tutti i criteri possibili si trova nella documentazione della funzione pywinauto.findwindows.find_elements(…).

La magia dell'accesso tramite attributo e chiave

Python semplifica la creazione di specifiche della finestra e riconosce dinamicamente gli attributi dell'oggetto (il metodo è stato ridefinito all'interno di __getattribute__). Naturalmente, ai nomi degli attributi si applicano le stesse restrizioni che per il nome di qualsiasi variabile (non è possibile inserire spazi, virgole e altri caratteri speciali). Fortunatamente, pywinauto utilizza un algoritmo di ricerca chiamato «best match», che è resistente a errori di battitura e piccole variazioni.

app.UntitledNotepad
# è equivalente a
app.window(best_match='UntitledNotepad')

Se hai bisogno di stringhe Unicode (ad esempio per la lingua russa), spazi ecc., puoi accedere tramite la chiave (come se fosse un normale dizionario):

app['Untitled - Notepad']
# è equivalente a
app.window(best_match='Untitled - Notepad')

Cinque regole per nomi magici

Come scoprire i nomi magici di riferimento? Quelli che vengono assegnati all'elemento prima della ricerca. Se hai fornito un nome sufficientemente simile al riferimento, l'elemento verrà trovato.

  1. Per titolo (testo, nome): app.Properties.OK.click()
  2. Per testo e tipo di elemento: app.Properties.OKButton.click()
  3. Per tipo e numero: app.Properties.Button3.click() (i nomi Button0 e Button1 sono legati al primo elemento trovato, Button2 — al secondo, e così via in ordine — così è storicamente accaduto)
  4. Per testo statico (a sinistra o sopra) e per tipo: app.OpenDialog.FileNameEdit.set_text("") (utile per elementi con testo dinamico)
  5. Per tipo e per testo interno: app.Properties.TabControlSharing.select("General")

Di solito, due o tre regole vengono applicate contemporaneamente, raramente di più. Per verificare quali nomi specifici sono disponibili per ciascun elemento, è possibile utilizzare il metodo print_control_identifiers(). Può stampare l'albero degli elementi sia sullo schermo che in un file. Per ogni elemento vengono stampati i suoi nomi magici di riferimento. È inoltre possibile copiare da lì specifiche più dettagliate degli elementi figli. Il risultato nello script apparirà così:

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

L'albero degli elementi stesso è di solito un lungo elenco piuttosto grande.

>>> 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(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(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(title="Type:", auto_id="13080", control_type="Text")
   |
   | Edit - 'Type:'    (L790, T643, R1036, B666)
   | [u'4', 'Edit2', u'Type:Edit']
   | child_window(title="Type:", auto_id="13059", control_type="Edit")
   |
   | Static - 'Location:'    (L717, T669, R780, B684)
   | [u'Location:Static', u'Location:', u'Static2']
   | child_window(title="Location:", auto_id="13089", control_type="Text")
   |
   | Edit - 'Location:'    (L790, T669, R1036, B692)
   | ['Edit3', u'Location:Edit', u'5']
   | child_window(title="Location:", auto_id="13065", control_type="Edit")
   |
   | Static - 'Size:'    (L717, T695, R780, B710)
   | [u'Size:Static', u'Size:', u'Static3']
   | child_window(title="Size:", auto_id="13081", control_type="Text")
   |
   | Edit - 'Size:'    (L790, T695, R1036, B718)
   | ['Edit4', u'6', u'Size:Edit']
   | child_window(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(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(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(title="Contains:", auto_id="13088", control_type="Text")
   |
   | Edit - 'Contains:'    (L790, T747, R1036, B770)
   | [u'8', 'Edit6', u'Contains:Edit']
   | child_window(title="Contains:", auto_id="13087", control_type="Edit")
   |
   | Image - 'Contains:'    (L717, T773, R1035, B775)
   | [u'Contains:Image', 'Image3', u'Contains:2']
   | child_window(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(title="Created:", auto_id="13092", control_type="Text")
   |
   | Edit - 'Created:'    (L790, T786, R1036, B809)
   | [u'Created:Edit', 'Edit7', u'9']
   | child_window(title="Created:", auto_id="13072", control_type="Edit")
   |
   | Image - 'Created:'    (L717, T812, R1035, B814)
   | [u'Created:Image', 'Image4', u'Created:2']
   | child_window(title="Created:", auto_id="13097", control_type="Image")
   |
   | Static - 'Attributes:'    (L717, T825, R780, B840)
   | [u'Attributes:Static', u'Static7', u'Attributes:']
   | child_window(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(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(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(title="Advanced...", 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 - 'Cancel'    (L895, T968, R970, B991)
   | ['Button3', u'CancelButton', u'Cancel']
   | child_window(title="Cancel", auto_id="2", control_type="Button")
   |
   | Button - 'Apply'    (L976, T968, R1051, B991)
   | ['Button4', u'ApplyButton', u'Apply']
   | child_window(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(title="General", control_type="TabItem")
   |    |
   |    | TabItem - 'Sharing'    (L753, T558, R801, B576)
   |    | [u'Sharing', u'SharingTabItem', 'TabItem2']
   |    | child_window(title="Sharing", control_type="TabItem")
   |    |
   |    | TabItem - 'Security'    (L801, T558, R851, B576)
   |    | [u'Security', 'TabItem3', u'SecurityTabItem']
   |    | child_window(title="Security", control_type="TabItem")
   |    |
   |    | TabItem - 'Previous Versions'    (L851, T558, R947, B576)
   |    | [u'Previous VersionsTabItem', u'Previous Versions', 'TabItem4']
   |    | child_window(title="Previous Versions", control_type="TabItem")
   |    |
   |    | TabItem - 'Customize'    (L947, T558, R1007, B576)
   |    | [u'CustomizeTabItem', 'TabItem5', u'Customize']
   |    | child_window(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(title="System", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | MenuItem - 'System'    (L696, T526, R718, B548)
   |    |    | [u'System2', u'MenuItem', u'SystemMenuItem']
   |    |    | child_window(title="System", control_type="MenuItem")
   |    |
   |    | Button - 'Close'    (L1024, T519, R1058, B549)
   |    | [u'CloseButton', u'Close', 'Button5']
   |    | child_window(title="Close", control_type="Button")

In alcuni casi, stampare l'intero albero può rallentare (ad esempio, su iTunes ci sono addirittura tremila elementi in una scheda!), ma è possibile utilizzare il parametro depth (profondità): depth=1 — l'elemento stesso, depth=2 — solo i figli diretti e così via. Può essere specificato anche nelle specifiche quando si crea child_window.

Esempi

Aggiorniamo costantemente l'elenco degli esempi nel repository. Tra i più recenti, vale la pena menzionare l'automazione dell'analizzatore di rete WireShark (ottimo esempio di applicazione Qt5; anche se questo compito può essere svolto senza GUI, poiché esiste scapy.Sniffer dal pacchetto Python scapy). C'è anche un esempio di automazione di MS Paint con la sua barra degli strumenti Ribbon.

Un altro ottimo esempio, scritto dal mio studente: il trascinamento di un file da explorer.exe su una pagina di Chrome per Google Drive (decisamente entrerà nel repository principale un po' più tardi).

E, naturalmente, un esempio di registrazione agli eventi della tastiera (hot keys) e del mouse:
hook_and_listen.py.

Ringraziamenti

Un ringraziamento speciale a coloro che aiutano costantemente a far crescere il progetto. Per me e Valentina è un hobby continuo. Due dei miei studenti della NNGU hanno recentemente conseguito la laurea su questo tema. Alexander ha dato un grande contributo al supporto di MS UI Automation e ha recentemente iniziato a sviluppare un generatore di codice automatico basato sul principio di "registrazione e riproduzione" delle proprietà testuali (è la funzionalità più complessa), attualmente solo per il backend "uia". Ivan sta sviluppando un nuovo backend per Linux basato su AT-SPI (moduli mouse e tastiera basato su python-xlib — già nelle versioni 0.6.x).

Dato che insegno un corso speciale sull'automazione con Python da un po', alcuni studenti di master completano i compiti a casa implementando piccole funzionalità o esempi di automazione. Alcuni aspetti chiave sono stati scoperti anche da studenti durante la fase di ricerca. Anche se a volte bisogna prestare attenzione alla qualità del codice. Questo è fortemente facilitato da analizzatori statici (QuantifiedCode, Codacy e Landscape) e da test automatici nel cloud (servizio AppVeyor) con una copertura del codice attorno al 95%.

Un ringraziamento a tutti coloro che Lasciate feedback, segnalate bug e inviate richieste di pull!

Risorse aggiuntive

Seguiamo le domande tramite l'etichetta su StackOverflow (di recente è stata aggiunta l'etichetta nella versione russa di SO) e per parola chiave su Toster. C'è chat in lingua russa su Gitter.

Aggiorniamo mensilmente il ranking delle librerie open-source per il testing GUI. Solo Autohotkey (con una comunità molto ampia e una lunga storia) e PyAutoGUI (in gran parte grazie alla popolarità dei libri del suo autore Al Sweigart: «Automate the Boring Stuff with Python» e altri) crescono più velocemente per numero di stelle su GitHub.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster