Automatizziamo il GUI desktop in Python + pywinauto: come integrare MS UI Automation

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

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

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

Questo articolo è in parte una trascrizione della presentazione alla conferenza SQA Days 20 a Minsk (registrazione video e diapositive), in parte una versione russa Guida all'Inizio per pywinauto.

Cominciamo con una breve panoramica dell'open source in questo campo. Per le applicazioni GUI desktop, tutto è un po' più complicato rispetto al web, che ha Selenium. Ecco i principali approcci:

Metodo delle coordinate

Hardcoding i punti di clic, sperando in colpi fortunati.
[+] Cross-platform, facilmente realizzabile.
[+] Facile creare una registrazione test 'record-replay'.
[-] Il più instabile in relazione ai cambiamenti di risoluzione dello schermo, temi, caratteri, dimensioni delle finestre, ecc.
[-] Richiede enormi sforzi per il supporto, spesso è più semplice rigenerare i test da zero o testarli manualmente.
[-] Automatizza solo le azioni, per la verifica e l'estrazione dei dati ci sono altri metodi.

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 gli altri approcci. Ad esempio, per grafiche personalizzate è possibile cliccare sulle coordinate relative (dall'angolo in alto a sinistra della finestra/elemento, e non dell'intero schermo) — di solito è abbastanza affidabile, specialmente se si considera la lunghezza/larghezza dell'intero elemento (quindi una diversa risoluzione dello schermo non è un problema).

Un'altra opzione: scegliere per i test solo una macchina con impostazioni stabili (non cross-platform, ma in alcuni casi va bene).

Riconoscimento delle immagini di riferimento

[+] Cross-platform
[+-] Relativamente affidabile (meglio del metodo delle coordinate), ma richiede comunque alcune astuzie.
[-+] Relativamente lento, poiché richiede risorse CPU per gli algoritmi di riconoscimento.
[-] Sul riconoscimento del testo (OCR), di solito non si parla => non è possibile ottenere dati testuali. Per quanto ne so, le attuali soluzioni OCR non sono molto affidabili per questo tipo di compiti e non hanno un utilizzo diffuso (benvenuti nei commenti, se 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 è stato disegnato dal sistema o dal framework.
[+] Consente di estrarre dati testuali => più facile verificare i risultati dei test.
[+] Di solito, il più veloce, poiché consuma quasi nessuna risorsa CPU.
[-] Difficile creare uno strumento cross-platform: praticamente tutte le librerie open-source supportano 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 è realmente disponibile. Ad esempio, il test della schermata di avvio all'interno di VirtualBox — qui è impossibile fare a meno del riconoscimento delle immagini. Ma in molti casi classici, l'approccio all'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 cross-platform (più precisamente una famiglia di librerie) basato su tecnologie di accessibilità. Tuttavia, non è molto popolare. Non l'ho utilizzato personalmente, ma da quello che ho sentito, l'interfaccia non è delle più comode. Se ci sono feedback positivi, vi prego di condividerli nei commenti.

Backdoor di test (alias. bicicletta interna)

Per le applicazioni cross-platform, gli stessi sviluppatori spesso creano un meccanismo interno per garantire la testabilità. Ad esempio, creano un server TCP di servizio nell'applicazione, i test si connettono a esso e inviano comandi testuali: 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 state costruite in un modo o nell'altro su Win32 API. In particolare, MFC, WTL, C++ Builder, Delphi, VB6 — tutti questi strumenti utilizzano Win32 API. Anche Windows Forms sono in gran parte compatibili con Win32 API.

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

Microsoft UI Automation

Il principale vantaggio: la tecnologia MS UI Automation supporta la stragrande maggioranza delle applicazioni GUI su Windows con rare eccezioni. Problema: non è molto più facile da studiare rispetto a Win32 API. Altrimenti, nessuno farebbe wrapper su di essa.

Di fatto, 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. A proposito, presenta un bug introdotto, che può causare l'omissione di alcuni elementi UI. Pertanto, è meglio utilizzare UIAutomationCore.dll direttamente (se avete sentito parlare di UiaComWrapper in C#, è proprio quello).

Varietà di interfacce COM:

(1) IUknown di base — «la radice di tutti i mali». Il più basso livello, per niente user-friendly.
(2) IDispatch e le derivati (ad esempio, Excel.Application), che possono essere usati in Python tramite il pacchetto win32com.client (incluso in pyWin32). La soluzione più comoda e bella.
(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 cui codice sorgente delle wrapper in C su UIAutomationCore.dll non è rilasciato), RAutomation in Ruby.

AT-SPI

Nonostante quasi tutte le distribuzioni della famiglia Linux siano basate su X Window System (in Fedora 25 hanno cambiato gli 'x' in Wayland), gli 'x' possono operare solo su finestre di primo livello e mouse/tastiera. Per un'analisi dettagliata dei pulsanti, delle caselle di selezione e così via — esiste la tecnologia AT-SPI. I più popolari gestori di finestre hanno un così detto demone del registro AT-SPI, che fornisce un GUI automatizzabile per le applicazioni (almeno Qt e GTK sono supportati).

Strumenti: pyatspi2.

pyatspi2, a mio avviso, contiene troppe dipendenze come PyGObject. La tecnologia stessa è disponibile sotto forma di una normale libreria dinamica libatspi.so. Ha Reference Manual. Per la libreria pywinauto, stiamo pianificando di implementare il supporto per AT-SPI proprio in questo modo: attraverso il caricamento di libatspi.so e il modulo ctypes. C'è solo un piccolo problema relativo all'uso della versione corretta, poiché per le applicazioni GTK+ e Qt sono leggermente diverse. Si prevede che il rilascio di pywinauto 0.7.0 con supporto completo per Linux possa avvenire nella prima metà del 2018.

Apple Accessibility API

Su MacOS c'è un proprio linguaggio di automazione, AppleScript. Per implementare qualcosa di simile su Python, ovviamente, è necessario utilizzare funzioni da ObjectiveC. A partire, sembra, da MacOS 10.6, il pacchetto pyobjc è incluso nel Python preinstallato. Ciò 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 LDTP, ma è anche una libreria autonoma. Esiste 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 risorsa.

Come iniziare a utilizzare pywinauto

Per prima cosa, è consigliabile dotarsi di un ispezionatore GUI degli oggetti (quello che viene chiamato Spy tool). Questo aiuta a esaminare l'applicazione dall'interno: come è strutturata la gerarchia degli elementi e quali proprietà sono disponibili. Gli ispetti più noti degli oggetti sono:

  • Spy++ — è incluso nella fornitura di Visual Studio, comprese le versioni Express o Community Edition. Usa l'API Win32. È noto anche 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'ispezionatore stesso, è necessario selezionare la modalità UI Automation invece di MS AA (Active Accessibility, predecessore di UI Automation).

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

  • backend=»win32″ — è attualmente utilizzato come predefinito, funziona bene con MFC, WTL, VB6 e altre applicazioni legacy.
  • backend=»uia» — nuovo backend per MS UI Automation: funziona perfettamente con WPF e WinForms; è anche buono per le applicazioni Delphi e Windows Store; funziona con Qt5 e alcune applicazioni Java. In generale, se Inspect.exe vede gli elementi e le loro proprietà, significa che questo backend è adatto. In linea di principio, la maggior parte dei browser supporta anche UI Automation (Mozilla per impostazione predefinita, mentre a Chrome deve essere fornita la chiave della riga di comando --force-renderer-accessibility, per vedere gli elementi sulle pagine in Inspect.exe). Certo, è difficile competere con Selenium in questo campo. È semplicemente un altro modo di interagire con il browser (può essere utile per scenari cross-prodotto).

Punti di accesso per l'automazione

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

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

# Definiamo la finestra che vogliamo trovare nel processo Notepad.exe
dlg_spec = app.UntitledNotepad
# aspettiamo che la finestra appaia realmente
actionable_dlg = dlg_spec.wait('visible')

Se vuoi gestire più applicazioni allo stesso tempo, ti aiuterà la classe Desktop. Ad esempio, nel calcolatore di Win10, la gerarchia degli elementi è sparsa su diversi processi (non solo calc.exe). Quindi non puoi fare a meno dell'oggetto Desktop .

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 in cui è necessario specificare il backend. Tutto il resto si colloca in modo trasparente nel concetto di «specifica->wrapper», di cui parleremo in seguito.

Specifiche delle finestre/elementi

Questa è la principale concezione su cui si basa l'interfaccia 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) conserva i criteri che devono essere utilizzati per trovare la finestra o l'elemento reale.

Esempio di specifica dettagliata della finestra:

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

>>> dlg_spec


>>> dlg_spec.wrapper_object()

La ricerca della finestra avviene attraverso la chiamata al metodo .wrapper_object(). Restituisce un certo «wrapper» per la finestra/l'elemento reale oppure solleva ElementNotFoundError (a volte ElementAmbiguousError, se vengono trovati più elementi, sarà necessario specificare meglio i criteri di ricerca). Questo «wrapper» è già in grado di compiere alcune azioni con l'elemento o di estrarne dati.

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

dlg_spec.wrapper_object().minimizzare() # debugging
dlg_spec.minimizzare() # 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 possibili criteri è nei documenti della funzione pywinauto.findwindows.find_elements(…).

Magia dell'accesso per attributo e per chiave

Python semplifica la creazione di specifiche per le finestre e riconosce dinamicamente gli attributi dell'oggetto (il metodo viene sovrascritto __getattribute__). Naturalmente, sul nome dell'attributo ci sono le stesse limitazioni che si applicano a qualsiasi nome di variabile (non è possibile inserire spazi, virgole e altri simboli speciali). Per fortuna, pywinauto utilizza il cosiddetto algoritmo di ricerca "best match", che è resistente agli errori di battitura e a piccole variazioni.

app.UntitledNotepad
# è lo stesso di
app.window(best_match='UntitledNotepad')

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

app['Untitled - Notepad']
# è lo stesso di
app.window(best_match='Untitled - Notepad')

Cinque regole per nomi magici

Come si fa a conoscere i nomi magici standard? Quelli che vengono assegnati all'elemento prima della ricerca. Se hai specificato un nome sufficientemente simile allo standard, 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 — è una tradizione storica)
  4. Per testo statico (a sinistra o sopra) e per tipo: app.OpenDialog.FileNameEdit.set_text("") (utile per gli elementi con testo dinamico)
  5. Per tipo e testo interno: app.Properties.TabControlSharing.select("Generale")

Di solito si applicano due o tre regole contemporaneamente, raramente di più. Per verificare quali nomi specifici sono disponibili per ciascun elemento, puoi 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 standard. È anche 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 documento piuttosto lungo.

>>> app.Properties.print_control_identifiers()

Identificatori di Controllo:

Dialogo - 'Proprietà di Windows NT'    (L688, T518, R1065, B1006)
[u'Windows NT PropertiesDialog', u'Dialog', u'Windows NT Properties']
child_window(title="Proprietà di Windows NT", control_type="Window")
   |
   | Immagine - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Image1', u'Image0', 'Image', u'1']
   | child_window(auto_id="13057", control_type="Image")
   |
   | Immagine - ''    (L717, T630, R1035, B632)
   | ['Immagine2', u'2']
   | child_window(auto_id="13095", control_type="Image")
   |
   | Modifica - 'Nome della cartella:'    (L790, T596, R1036, B619)
   | [u'3', 'Edit', u'Edit1', u'Edit0']
   | child_window(title="Nome della cartella:", auto_id="13156", control_type="Edit")
   |
   | Statico - 'Tipo:'    (L717, T643, R780, B658)
   | [u'Tipo:Static', u'Static', u'Static1', u'Static0', u'Tipo:']
   | child_window(title="Tipo:", auto_id="13080", control_type="Text")
   |
   | Modifica - 'Tipo:'    (L790, T643, R1036, B666)
   | [u'4', 'Edit2', u'Tipo:Edit']
   | child_window(title="Tipo:", auto_id="13059", control_type="Edit")
   |
   | Statico - 'Posizione:'    (L717, T669, R780, B684)
   | [u'Posizione:Static', u'Posizione:', u'Static2']
   | child_window(title="Posizione:", auto_id="13089", control_type="Text")
   |
   | Modifica - 'Posizione:'    (L790, T669, R1036, B692)
   | ['Edit3', u'Posizione:Edit', u'5']
   | child_window(title="Posizione:", auto_id="13065", control_type="Edit")
   |
   | Statico - 'Dimensione:'    (L717, T695, R780, B710)
   | [u'Dimensione:Static', u'Dimensione:', u'Static3']
   | child_window(title="Dimensione:", auto_id="13081", control_type="Text")
   |
   | Modifica - 'Dimensione:'    (L790, T695, R1036, B718)
   | ['Edit4', u'6', u'Dimensione:Edit']
   | child_window(title="Dimensione:", auto_id="13064", control_type="Edit")
   |
   | Statico - 'Dimensione su disco:'    (L717, T721, R780, B736)
   | [u'Dimensione su disco:', u'Dimensione su disco:Static', u'Static4']
   | child_window(title="Dimensione su disco:", auto_id="13107", control_type="Text")
   |
   | Modifica - 'Dimensione su disco:'    (L790, T721, R1036, B744)
   | ['Edit5', u'7', u'Dimensione su disco:Edit']
   | child_window(title="Dimensione su disco:", auto_id="13106", control_type="Edit")
   |
   | Statico - 'Contiene:'    (L717, T747, R780, B762)
   | [u'Contiene:1', u'Contiene:0', u'Contiene:Static', u'Static5', u'Contiene:']
   | child_window(title="Contiene:", auto_id="13088", control_type="Text")
   |
   | Modifica - 'Contiene:'    (L790, T747, R1036, B770)
   | [u'8', 'Edit6', u'Contiene:Edit']
   | child_window(title="Contiene:", auto_id="13087", control_type="Edit")
   |
   | Immagine - 'Contiene:'    (L717, T773, R1035, B775)
   | [u'Contiene:Image', 'Image3', u'Contiene:2']
   | child_window(title="Contiene:", auto_id="13096", control_type="Image")
   |
   | Statico - 'Creato:'    (L717, T786, R780, B801)
   | [u'Creato:', u'Creato:Static', u'Static6', u'Creato:1', u'Creato:0']
   | child_window(title="Creato:", auto_id="13092", control_type="Text")
   |
   | Modifica - 'Creato:'    (L790, T786, R1036, B809)
   | [u'Creato:Edit', 'Edit7', u'9']
   | child_window(title="Creato:", auto_id="13072", control_type="Edit")
   |
   | Immagine - 'Creato:'    (L717, T812, R1035, B814)
   | [u'Creato:Image', 'Image4', u'Creato:2']
   | child_window(title="Creato:", auto_id="13097", control_type="Image")
   |
   | Statico - 'Attributi:'    (L717, T825, R780, B840)
   | [u'Attributi:Static', u'Static7', u'Attributi:']
   | child_window(title="Attributi:", auto_id="13091", control_type="Text")
   |
   | CheckBox - 'Sola lettura (Si applica solo ai file nella cartella)'    (L790, T825, R1035, B841)
   | [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Sola lettura (Si applica solo ai file nella cartella)CheckBox', u'Sola lettura (Si applica solo ai file nella cartella)']
   | child_window(title="Sola lettura (Si applica solo ai file nella cartella)", auto_id="13075", control_type="CheckBox")
   |
   | CheckBox - 'Nascosto'    (L790, T848, R865, B864)
   | ['CheckBox2', u'NascostoCheckBox', u'Nascosto']
   | child_window(title="Nascosto", auto_id="13076", control_type="CheckBox")
   |
   | Pulsante - 'Avanzate...'    (L930, T845, R1035, B868)
   | [u'Avanzate...', u'Avanzate...Button', 'Button', u'Button1', u'Button0']
   | child_window(title="Avanzate...", auto_id="13154", control_type="Button")
   |
   | Pulsante - 'OK'    (L814, T968, R889, B991)
   | ['Button2', u'OK', u'OKButton']
   | child_window(title="OK", auto_id="1", control_type="Button")
   |
   | Pulsante - 'Annulla'    (L895, T968, R970, B991)
   | ['Button3', u'CancelButton', u'Annulla']
   | child_window(title="Annulla", auto_id="2", control_type="Button")
   |
   | Pulsante - 'Applica'    (L976, T968, R1051, B991)
   | ['Button4', u'ApplyButton', u'Applica']
   | child_window(title="Applica", auto_id="12321", control_type="Button")
   |
   | TabControl - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabControlCondivisione', u'TabControlVersioni Precedenti', u'TabControlSicurezza', u'TabControl', u'TabControlPersonalizza']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'Generale'    (L704, T558, R753, B576)
   |    | [u'GeneraleTabItem', 'TabItem', u'Generale', u'TabItem0', u'TabItem1']
   |    | child_window(title="Generale", control_type="TabItem")
   |    |
   |    | TabItem - 'Condivisione'    (L753, T558, R801, B576)
   |    | [u'Condivisione', u'CondivisioneTabItem', 'TabItem2']
   |    | child_window(title="Condivisione", control_type="TabItem")
   |    |
   |    | TabItem - 'Sicurezza'    (L801, T558, R851, B576)
   |    | [u'Sicurezza', 'TabItem3', u'SicurezzaTabItem']
   |    | child_window(title="Sicurezza", control_type="TabItem")
   |    |
   |    | TabItem - 'Versioni Precedenti'    (L851, T558, R947, B576)
   |    | [u'Versioni PrecedentiTabItem', u'Versioni Precedenti', 'TabItem4']
   |    | child_window(title="Versioni Precedenti", control_type="TabItem")
   |    |
   |    | TabItem - 'Personalizza'    (L947, T558, R1007, B576)
   |    | [u'PersonalizzaTabItem', 'TabItem5', u'Personalizza']
   |    | child_window(title="Personalizza", control_type="TabItem")
   |
   | Barra del Titolo - 'Nessuno'    (L712, T521, R1057, B549)
   | ['TitleBar', u'11']
   |    |
   |    | Menu - 'Sistema'    (L696, T526, R718, B548)
   |    | [u'Sistema0', u'Sistema', u'Sistema1', u'Menu', u'MenuSistema']
   |    | child_window(title="Sistema", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | Elemento di Menu - 'Sistema'    (L696, T526, R718, B548)
   |    |    | [u'Sistema2', u'ElementoDiMenu', u'ElementoMenuSistema']
   |    |    | child_window(title="Sistema", control_type="MenuItem")
   |    |
   |    | Pulsante - 'Chiudi'    (L1024, T519, R1058, B549)
   |    | [u'CloseButton', u'Chiudi', 'Button5']
   |    | child_window(title="Chiudi", control_type="Button")

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

Esempi

Stiamo continuamente aggiornando l'elenco degli esempi nel repository. Tra le novità, vale la pena menzionare l'automazione dell'analizzatore di rete WireShark (un buon esempio di applicazione Qt5; anche se questo compito può essere risolto senza GUI, dato che esiste scapy.Sniffer del 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 Chrome per Google Drive (questo verrà spostato nel repository principale un po' più tardi).

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

Ringraziamenti

Un ringraziamento speciale a coloro che aiutano costantemente a sviluppare il progetto. Per me e Valentina è un hobby costante. Due dei miei studenti di NNGU hanno recentemente sostenuto la laurea triennale su questo tema. Alexandr ha contribuito notevolmente al supporto di MS UI Automation e ha recentemente iniziato a creare un generatore di codice automatico basato sul principio "registrazione-riproduzione" basato su proprietà testuali (è la funzionalità più complessa), al momento 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).

Poiché ho tenuto per molto tempo un corso specialistico sull'automazione in Python, parte degli studenti di magistrale svolgono compiti a casa implementando piccole funzionalità o esempi di automazione. Alcune cose chiave in fase di ricerca sono state scoperte proprio dagli studenti. Sebbene a volte sia necessario prestare particolare attenzione alla qualità del codice. A questo proposito, gli analizzatori statici (QuantifiedCode, Codacy e Landscape) e i test automatici nel cloud (servizio AppVeyor) con una copertura del codice intorno al 95% sono molto utili.

Un ringraziamento anche a tutti coloro che lasciano feedback, segnalano bug e inviano richieste di pull!

Risorse aggiuntive

Seguiamo le domande tramite il tag su StackOverflow (è recentemente apparso un tag nella versione russa di SO) e per la parola chiave su Toster. C'è una chat in russo su Gitter.

Aggiorniamo ogni mese la classifica delle librerie open-source per testare l'interfaccia grafica.. Solo Autohotkey (che ha una comunità molto ampia e una lunga storia) e PyAutoGUI (in gran parte grazie alla popolarità dei libri del suo autore Al Sweigart: «Automatizza le cose noiose con Python» e altri) crescono più velocemente in termini 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