Automatizando GUI de escritorio en Python + pywinauto: cómo integrar MS UI Automation

Biblioteca de Python pywinauto es un proyecto de código abierto para la automatización de aplicaciones GUI de escritorio en Windows. En los últimos dos años, se han añadido nuevas características importantes:

  • Soporte para la tecnología MS UI Automation. La interfaz permanece igual, y ahora son compatibles: WinForms, WPF, Qt5, Windows Store (UWP) y más, casi todo lo que existe en Windows.
  • Sistema de backends/plugin (actualmente hay dos debajo del capó: el predeterminado "win32" y el nuevo "uia"). Ahora avanzamos suavemente hacia la multiplataforma.
  • Ganchos de Win32 para el mouse y el teclado (hot keys al estilo de pyHook).

También haremos una pequeña revisión de lo que hay en el código abierto para la automatización de escritorio (sin pretensiones de una comparación seria).

Este artículo es parcialmente la transcripción de una presentación en la conferencia SQA Days 20 en Minsk (grabación en video y diapositivas), parcialmente la versión en español Guía de inicio para pywinauto.

Comencemos con una breve revisión del código abierto en este campo. Para aplicaciones GUI de escritorio, todo es un poco más complicado que para la web, que tiene Selenium. Aquí están los enfoques principales:

Método de coordenadas

Codificamos las coordenadas de clics, esperando acertar.
[+] Multiplataforma, fácil de implementar.
[+] Fácil de hacer grabación-reproducción de pruebas.
[-] El más inestable debido a cambios en la resolución de pantalla, temas, fuentes, tamaños de ventanas, etc.
[-] Se requieren esfuerzos enormes para el mantenimiento, a menudo es más sencillo regenerar las pruebas desde cero o probar manualmente.
[-] Automatiza solo acciones, existen otros métodos para verificación y extracción de datos.

Herramientas (multiplataforma): autopy, PyAutoGUI, PyUserInput y muchos otros. Por lo general, herramientas más complejas incluyen esta funcionalidad (no siempre de forma multiplataforma).

Cabe destacar que el método de coordenadas puede complementar otros enfoques. Por ejemplo, para gráficos personalizados se puede hacer clic en coordenadas relativas (desde la esquina superior izquierda de la ventana/elemento, y no de toda la pantalla) — generalmente esto es bastante confiable, especialmente si se considera la longitud/anchura de todo el elemento (entonces distintas resoluciones de pantalla no serán un problema).

Otra opción: reservar una sola máquina con configuraciones estables para pruebas (no es multiplataforma, pero en algunos casos es útil).

Reconocimiento de imágenes de referencia

[+] Multiplataforma
[+-] Relativamente confiable (mejor que el método de coordenadas), pero aún así requiere ingenio.
[-+] Relativamente lento, ya que requiere recursos de CPU para los algoritmos de reconocimiento.
[-] Sobre el reconocimiento de texto (OCR), por lo general no se habla de él => no se pueden obtener datos textuales. Hasta donde sé, las soluciones OCR existentes no son muy confiables para este tipo de tareas y no tienen un uso extendido (bienvenidos los comentarios si esto ha cambiado).

Herramientas: Sikuli, Lackey (compatible con Sikuli, en Python puro), PyAutoGUI.

Tecnologías de accesibilidad

[+] El método más confiable, ya que permite buscar por texto, independientemente de cómo ha sido presentado por el sistema o el marco de trabajo.
[+] Permite extraer datos textuales => más fácil verificar los resultados de las pruebas.
[+] Por lo general, el más rápido, ya que casi no consume recursos de CPU.
[-] Difícil hacer una herramienta multiplataforma: absolutamente todas las bibliotecas de código abierto solo soportan una o dos tecnologías de accesibilidad. Nadie soporta Windows/Linux/MacOS en conjunto, excepto las de pago como TestComplete, UFT o Squish.
[-] No siempre está disponible tal tecnología en principio. Por ejemplo, la prueba de la pantalla de inicio dentro de VirtualBox no se puede hacer sin reconocimiento de imágenes. Pero en muchos casos clásicos, el enfoque de accesibilidad sigue siendo aplicable. De eso trataremos más adelante.

Herramientas: TestStack.White en C#, Winium.Desktop en C# (compatible con Selenium), MS WinAppDriver en C# (compatible con Appium), pywinauto, pyatom (compatible con LDTP), Python-UIAutomation-for-Windows, RAutomation en Ruby, LDTP (Proyecto de Pruebas de Escritorio en Linux) y su versión para Windows Cobra.

LDTP es probablemente la única herramienta multiplataforma de código abierto (mejor dicho, un conjunto de bibliotecas) basada en tecnologías de accesibilidad. Sin embargo, no es muy popular. No la he utilizado, pero por los comentarios, su interfaz no es la más amigable. Si hay opiniones positivas, por favor compártanlas en los comentarios.

Backdoor de prueba (a.k.a. bicicleta interna)

Para aplicaciones multiplataforma, los propios desarrolladores a menudo crean un mecanismo interno para garantizar la testabilidad. Por ejemplo, crean un servidor TCP en la aplicación, los tests se conectan a él y envían comandos de texto: a qué hacer clic, de dónde obtener datos, etc. Confiable, pero no universal.

Tecnologías de accesibilidad de escritorio principales

El viejo y querido Win32 API

La mayoría de las aplicaciones de Windows escritas antes del lanzamiento de WPF y luego de Windows Store se construyeron de una forma u otra sobre la API de Win32. En concreto, MFC, WTL, C++ Builder, Delphi, VB6: todas estas herramientas utilizan la API de Win32. Incluso Windows Forms es, en gran medida, compatible con la API de Win32.

Herramientas: AutoIt (similar a VB) y un wrapper de Python pyautoit, AutoHotkey (lenguaje propio, tiene una interfaz COM IDispatch), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).

Microsoft UI Automation

La mayor ventaja: la tecnología de MS UI Automation es compatible con la abrumadora mayoría de las aplicaciones GUI en Windows, con pocas excepciones. El problema: no es mucho más fácil de aprender que la API de Win32. De lo contrario, nadie habría creado wrappers sobre ella.

De hecho, es un conjunto de interfaces COM personalizadas (en su mayoría, UIAutomationCore.dll), y también tiene un wrapper .NET en forma de namespace System.Windows.Automation. Por cierto, tiene un bug introducido que puede hacer que algunos elementos de la UI sean omitidos. Por lo tanto, es mejor usar UIAutomationCore.dll directamente (si has oído hablar de UiaComWrapper en C#, eso es).

Variedades de interfaces COM:

(1) Basado en IUknown — "la raíz de todos los males". El más de bajo nivel, no es nada amigable para el usuario.
(2) IDispatch y derivados (por ejemplo, Excel.Application), que se pueden usar en Python mediante el paquete win32com.client (parte de pyWin32). La opción más conveniente y elegante.
(3) Interfaces personalizadas, con las que trabaja el paquete de Python de terceros comtypes.

Herramientas: TestStack.White en C#, pywinauto 0.6.0+, Winium.Desktop en C#, Python-UIAutomation-for-Windows (su código fuente de wrappers en C sobre UIAutomationCore.dll no está disponible), RAutomation en Ruby.

AT-SPI

A pesar de que casi todas las distribuciones familiares de Linux están construidas sobre el Sistema X Window (en Fedora 25 se cambiaron los "X" por Wayland), los "X" solo permiten operar con ventanas de nivel superior y ratón/teclado. Para un análisis detallado de botones, cajas de lista, etc., existe la tecnología AT-SPI. Los gestores de ventanas más populares tienen un demonio de registro AT-SPI que proporciona a las aplicaciones un GUI automatizable (al menos se admiten Qt y GTK).

Herramientas: pyatspi2.

pyatspi2, en mi opinión, contiene demasiadas dependencias como las de PyGObject. La tecnología en sí está disponible en forma de una biblioteca dinámica convencional libatspi.so. Tiene acceso a Manual de referencia. Para la biblioteca pywinauto planeamos implementar el soporte de AT-SPI precisamente así: mediante la carga de libatspi.so y el módulo ctypes. Solo hay un pequeño problema en cuanto al uso de la versión correcta, ya que para aplicaciones GTK+ y Qt son un poco diferentes. Se puede esperar un lanzamiento probable de pywinauto 0.7.0 con soporte completo de Linux en la primera mitad de 2018.

Apple Accessibility API

En MacOS hay un lenguaje de automatización propio llamado AppleScript. Para implementar algo similar en Python, es necesario usar funciones de ObjectiveC. Desde, parece, MacOS 10.6, se incluye el paquete pyobjc en el Python preinstalado. Esto también facilitará la lista de dependencias para el soporte futuro en pywinauto.

Herramientas: Además del lenguaje Apple Script, vale la pena prestar atención a ATOMac, también conocido como pyatom. Es compatible en interfaz con LDTP, pero también es una biblioteca independiente. Hay un ejemplo de automatización de iTunes en macOS, escrito por uno de mis estudiantes. Hay un problema conocido: los temporizadores flexibles no funcionan (métodos waitFor*). Pero, en general, es una buena herramienta.

Cómo comenzar a trabajar con pywinauto

Lo primero es armarse con un inspector de objetos GUI (lo que llaman la herramienta Spy). Este ayudará a explorar la aplicación internamente: cómo está estructurada la jerarquía de elementos, qué propiedades están disponibles. Los inspectores de objetos más conocidos son:

  • Spy++ — viene incluido con Visual Studio, incluyendo la edición Express o Community. Usa la API Win32. También es conocido su clon AutoIt Window Info.
  • Inspect.exe — está incluido en el SDK de Windows. Si lo tienes instalado, puedes encontrarlo en la carpeta C:Program Files (x86)Windows Kits<winver>binx64. En el propio inspector, debes seleccionar el modo UI Automation en lugar de MS AA (Active Accessibility, antecesor de UI Automation).

Una vez que hayas explorado la aplicación, seleccionamos el backend que usaremos. Basta con indicar el nombre del backend al crear el objeto Application.

  • backend=»win32″ — se usa por defecto, funciona bien con MFC, WTL, VB6 y otras aplicaciones heredadas.
  • backend=»uia» — nuevo backend para MS UI Automation: funciona idealmente con WPF y WinForms; también es bueno para aplicaciones de Delphi y Windows Store; compatible con Qt5 y algunas aplicaciones de Java. En general, si Inspect.exe puede ver los elementos y sus propiedades, significa que este backend es adecuado. En principio, la mayoría de los navegadores también soportan UI Automation (Mozilla por defecto, mientras que a Chrome, al iniciar, se le debe pasar la clave de línea de comandos --force-renderer-accessibility, para ver los elementos en las páginas en Inspect.exe). Claro que, la competencia con Selenium en esta área probablemente no sea posible. Simplemente es otra forma de trabajar con el navegador (puede ser útil para un escenario entre productos).

Puntos de entrada para la automatización

La aplicación está suficientemente estudiada. Es hora de crear un objeto Application y ejecutarlo o unirse a uno ya en ejecución. No es simplemente un clon de la clase estándar subprocess.Popen, es decir, un objeto envolvente que limita todas sus acciones dentro de los límites del proceso. Esto es muy útil si se están ejecutando varias instancias de la aplicación y no se desea afectar a las demás.

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

# Describimos la ventana que queremos encontrar en el proceso Notepad.exe
dlg_spec = app.UntitledNotepad
# esperamos hasta que la ventana aparezca realmente
actionable_dlg = dlg_spec.wait('visible')

Si desea controlar varias aplicaciones a la vez, la clase Escritorio. Por ejemplo, en la calculadora de Win10, la jerarquía de elementos se extiende a varios procesos (no solo calc.exe). Así que no se puede prescindir del objeto Escritorio .

from subprocess import Popen
from pywinauto import Desktop

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

El objeto raíz (Application o Escritorio) es el único lugar donde se debe especificar el backend. Todo lo demás se integra de manera transparente en el concepto de «especificación->wrapper», que se explicará a continuación.

Especificaciones de ventanas/elementos

Este es el concepto principal sobre el que se construye la interfaz de pywinauto. Puede describir una ventana/elemento de manera aproximada o más detallada, incluso si aún no existe o ya está cerrada. La especificación de la ventana (objeto WindowSpecification) contiene los criterios que se deben utilizar para buscar la ventana o el elemento real.

Ejemplo de especificación de ventana detallada:

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

>>> dlg_spec


>>> dlg_spec.wrapper_object()

La búsqueda de la ventana se realiza mediante la llamada al método .wrapper_object(). Este devuelve un «wrapper» para la ventana/elemento real o lanza un ElementNotFoundError (a veces ElementAmbiguousError, si se encuentran varios elementos, es necesario aclarar el criterio de búsqueda). Este «wrapper» ya puede realizar algunas acciones con el elemento o obtener datos de él.

Python puede ocultar la llamada .wrapper_object(), de modo que el código final se vuelve más corto. Se recomienda utilizarlo solo para depuración. Las siguientes dos líneas hacen exactamente lo mismo:

dlg_spec.wrapper_object().minimize() # depuración
dlg_spec.minimize() # producción

Hay múltiples criterios de búsqueda para la especificación de la ventana. Aquí hay solo algunos ejemplos:

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

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

Hay una lista de todos los posibles criterios en la documentación de la función pywinauto.findwindows.find_elements(…).

La magia del acceso por atributo y por clave

Python facilita la creación de especificaciones de ventana y reconoce dinámicamente los atributos del objeto (dentro se ha redefinido el método __getattribute__). Por supuesto, los nombres de los atributos tienen las mismas restricciones que los nombres de cualquier variable (no se pueden insertar espacios, comas ni otros caracteres especiales). Afortunadamente, pywinauto utiliza un algoritmo de búsqueda denominado «best match», que es resistente a errores tipográficos y pequeñas variaciones.

app.UntitledNotepad
# lo mismo que
app.window(best_match='UntitledNotepad')

Si realmente se necesitan cadenas Unicode (por ejemplo, para el idioma ruso), espacios, etc., se puede acceder por clave (como si fuera un diccionario normal):

app['Untitled - Notepad']
# lo mismo que
app.window(best_match='Untitled - Notepad')

Cinco reglas para nombres mágicos

¿Cómo averiguar los nombres mágicos de referencia? Aquellos que se asignan al elemento antes de la búsqueda. Si ha especificado un nombre que es suficientemente parecido al original, significa que el elemento será encontrado.

  1. Por el título (texto, nombre): app.Properties.OK.click()
  2. Por el texto y el tipo de elemento: app.Properties.OKButton.click()
  3. Por el tipo y el número: app.Properties.Button3.click() (nombres Button0 y Button1 están vinculados al primer elemento encontrado, Button2 — al segundo, y a partir de ahí en orden — así ha sido históricamente)
  4. Por el texto estático (a la izquierda o arriba) y por el tipo: app.OpenDialog.FileNameEdit.set_text("") (útil para elementos con texto dinámico)
  5. Por el tipo y el texto dentro: app.Properties.TabControlSharing.select("General")

Normalmente se aplican dos o tres reglas a la vez, rara vez más. Para comprobar qué nombres específicos están disponibles para cada elemento, se puede usar el método print_control_identifiers(). Puede imprimir el árbol de elementos tanto en la pantalla como en un archivo. Para cada elemento, se imprimen sus nombres mágicos de referencia. También se puede copiar y pegar desde allí especificaciones más detalladas de los elementos secundarios. El resultado en el script se verá así:

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

El árbol de elementos en sí suele ser un gran documento.

>>> app.Properties.print_control_identifiers()

Identificadores de Control:

Diálogo - 'Propiedades de Windows NT'    (L688, T518, R1065, B1006)
[u'Propiedades de Windows NTDiálogo', u'Diálogo', u'Propiedades de Windows NT']
child_window(title="Propiedades de Windows NT", control_type="Window")
   |
   | Imagen - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Imagen1', u'Imagen0', 'Imagen', u'1']
   | child_window(auto_id="13057", control_type="Image")
   |
   | Imagen - ''    (L717, T630, R1035, B632)
   | ['Imagen2', u'2']
   | child_window(auto_id="13095", control_type="Image")
   |
   | Editar - 'Nombre de la carpeta:'    (L790, T596, R1036, B619)
   | [u'3', 'Editar', u'Editar1', u'Editar0']
   | child_window(title="Nombre de la carpeta:", auto_id="13156", control_type="Edit")
   |
   | Estático - 'Tipo:'    (L717, T643, R780, B658)
   | [u'Tipo:Estático', u'Estático', u'Estático1', u'Estático0', u'Tipo:']
   | child_window(title="Tipo:", auto_id="13080", control_type="Text")
   |
   | Editar - 'Tipo:'    (L790, T643, R1036, B666)
   | [u'4', 'Editar2', u'Tipo:Editar']
   | child_window(title="Tipo:", auto_id="13059", control_type="Edit")
   |
   | Estático - 'Ubicación:'    (L717, T669, R780, B684)
   | [u'Ubicación:Estático', u'Ubicación:', u'Estático2']
   | child_window(title="Ubicación:", auto_id="13089", control_type="Text")
   |
   | Editar - 'Ubicación:'    (L790, T669, R1036, B692)
   | ['Editar3', u'Ubicación:Editar', u'5']
   | child_window(title="Ubicación:", auto_id="13065", control_type="Edit")
   |
   | Estático - 'Tamaño:'    (L717, T695, R780, B710)
   | [u'Tamaño:Estático', u'Tamaño:', u'Estático3']
   | child_window(title="Tamaño:", auto_id="13081", control_type="Text")
   |
   | Editar - 'Tamaño:'    (L790, T695, R1036, B718)
   | ['Editar4', u'6', u'Tamaño:Editar']
   | child_window(title="Tamaño:", auto_id="13064", control_type="Edit")
   |
   | Estático - 'Tamaño en disco:'    (L717, T721, R780, B736)
   | [u'Tamaño en disco:', u'Tamaño en disco:Estático', u'Estático4']
   | child_window(title="Tamaño en disco:", auto_id="13107", control_type="Text")
   |
   | Editar - 'Tamaño en disco:'    (L790, T721, R1036, B744)
   | ['Editar5', u'7', u'Tamaño en disco:Editar']
   | child_window(title="Tamaño en disco:", auto_id="13106", control_type="Edit")
   |
   | Estático - 'Contiene:'    (L717, T747, R780, B762)
   | [u'Contiene:1', u'Contiene:0', u'Contiene:Estático', u'Estático5', u'Contiene:']
   | child_window(title="Contiene:", auto_id="13088", control_type="Text")
   |
   | Editar - 'Contiene:'    (L790, T747, R1036, B770)
   | [u'8', 'Editar6', u'Contiene:Editar']
   | child_window(title="Contiene:", auto_id="13087", control_type="Edit")
   |
   | Imagen - 'Contiene:'    (L717, T773, R1035, B775)
   | [u'Contiene:Imagen', 'Imagen3', u'Contiene:2']
   | child_window(title="Contiene:", auto_id="13096", control_type="Image")
   |
   | Estático - 'Creado:'    (L717, T786, R780, B801)
   | [u'Creado:', u'Creado:Estático', u'Estático6', u'Creado:1', u'Creado:0']
   | child_window(title="Creado:", auto_id="13092", control_type="Text")
   |
   | Editar - 'Creado:'    (L790, T786, R1036, B809)
   | [u'Creado:Editar', 'Editar7', u'9']
   | child_window(title="Creado:", auto_id="13072", control_type="Edit")
   |
   | Imagen - 'Creado:'    (L717, T812, R1035, B814)
   | [u'Creado:Imagen', 'Imagen4', u'Creado:2']
   | child_window(title="Creado:", auto_id="13097", control_type="Image")
   |
   | Estático - 'Atributos:'    (L717, T825, R780, B840)
   | [u'Atributos:Estático', u'Estático7', u'Atributos:']
   | child_window(title="Atributos:", auto_id="13091", control_type="Text")
   |
   | CheckBox - 'Solo lectura (Solo aplica a archivos en la carpeta)'    (L790, T825, R1035, B841)
   | [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Solo lectura (Solo aplica a archivos en la carpeta)CheckBox', u'Solo lectura (Solo aplica a archivos en la carpeta)']
   | child_window(title="Solo lectura (Solo aplica a archivos en la carpeta)", auto_id="13075", control_type="CheckBox")
   |
   | CheckBox - 'Oculto'    (L790, T848, R865, B864)
   | ['CheckBox2', u'OcultoCheckBox', u'Oculto']
   | child_window(title="Oculto", auto_id="13076", control_type="CheckBox")
   |
   | Botón - 'Avanzado...'    (L930, T845, R1035, B868)
   | [u'Avanzado...', u'Avanzado...Botón', 'Botón', u'Botón1', u'Botón0']
   | child_window(title="Avanzado...", auto_id="13154", control_type="Button")
   |
   | Botón - 'Aceptar'    (L814, T968, R889, B991)
   | ['Botón2', u'Aceptar', u'AceptarBotón']
   | child_window(title="Aceptar", auto_id="1", control_type="Button")
   |
   | Botón - 'Cancelar'    (L895, T968, R970, B991)
   | ['Botón3', u'CancelarBotón', u'Cancelar']
   | child_window(title="Cancelar", auto_id="2", control_type="Button")
   |
   | Botón - 'Aplicar'    (L976, T968, R1051, B991)
   | ['Botón4', u'AplicarBotón', u'Aplicar']
   | child_window(title="Aplicar", auto_id="12321", control_type="Button")
   |
   | TabControl - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabControlCompartir', u'TabControlVersiones Anteriores', u'TabControlSeguridad', u'TabControl', u'TabControlPersonalizar']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'General'    (L704, T558, R753, B576)
   |    | [u'TabItemGeneral', 'TabItem', u'General', u'TabItem0', u'TabItem1']
   |    | child_window(title="General", control_type="TabItem")
   |    |
   |    | TabItem - 'Compartir'    (L753, T558, R801, B576)
   |    | [u'Compartir', u'TabItemCompartir', 'TabItem2']
   |    | child_window(title="Compartir", control_type="TabItem")
   |    |
   |    | TabItem - 'Seguridad'    (L801, T558, R851, B576)
   |    | [u'Seguridad', 'TabItem3', u'TabItemSeguridad']
   |    | child_window(title="Seguridad", control_type="TabItem")
   |    |
   |    | TabItem - 'Versiones Anteriores'    (L851, T558, R947, B576)
   |    | [u'TabItemVersiones Anteriores', u'Versiones Anteriores', 'TabItem4']
   |    | child_window(title="Versiones Anteriores", control_type="TabItem")
   |    |
   |    | TabItem - 'Personalizar'    (L947, T558, R1007, B576)
   |    | [u'TabItemPersonalizar', 'TabItem5', u'Personalizar']
   |    | child_window(title="Personalizar", control_type="TabItem")
   |
   | Barra de Título - 'Ninguno'    (L712, T521, R1057, B549)
   | ['Barra de Título', u'11']
   |    |
   |    | Menú - 'Sistema'    (L696, T526, R718, B548)
   |    | [u'Sistema0', u'Sistema', u'Sistema1', u'Menú', u'MenúSistema']
   |    | child_window(title="Sistema", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | Elemento de Menú - 'Sistema'    (L696, T526, R718, B548)
   |    |    | [u'Sistema2', u'Elemento deMenú', u'ElementoMenúSistema']
   |    |    | child_window(title="Sistema", control_type="MenuItem")
   |    |
   |    | Botón - 'Cerrar'    (L1024, T519, R1058, B549)
   |    | [u'BotónCerrar', u'Cerrar', 'Botón5']
   |    | child_window(title="Cerrar", control_type="Button")

En algunos casos, imprimir todo el árbol puede ralentizarse (por ejemplo, en iTunes, ¡hay hasta tres mil elementos en una sola pestaña!), pero se puede usar el parámetro depth (profundidad): depth=1 — el mismo elemento, depth=2 — solo los hijos inmediatos, y así sucesivamente. También se puede especificar al crear child_window.

Ejemplos

Estamos constantemente ampliando la lista de ejemplos en el repositorio. Entre los nuevos, cabe destacar la automatización del analizador de red WireShark (es un buen ejemplo de aplicación Qt5; aunque esta tarea también se puede resolver sin GUI, ya que hay scapy.Sniffer del paquete de Python scapy). También hay un ejemplo de automatización de MS Paint con su barra de herramientas Ribbon.

Otro gran ejemplo, escrito por uno de mis estudiantes: arrastrar un archivo de explorer.exe a una página de Chrome para Google Drive (este se trasladará al repositorio principal un poco más tarde).

Y, por supuesto, un ejemplo de suscripción a eventos de teclado (teclas rápidas) y ratón:
hook_and_listen.py.

Agradecimientos

Un agradecimiento especial a aquellos que constantemente ayudan a desarrollar el proyecto. Para mí y Valentina es un pasatiempo constante. Dos de mis estudiantes de NNGU recientemente defendieron sus tesis de licenciatura sobre este tema. Alexander ha hecho una gran contribución al soporte de MS UI Automation y recientemente comenzó a desarrollar un generador de código automático basado en el principio de "grabar-reproducir" basado en propiedades de texto (esta es la característica más compleja), por ahora solo para el backend "uia". Ivan está desarrollando un nuevo backend para Linux basado en AT-SPI (los módulos mouse y keyboard basado en python-xlib ya están en las versiones 0.6.x).

Como he estado enseñando un curso especializado sobre automatización en Python durante bastante tiempo, parte de los estudiantes de maestría realizan tareas domésticas implementando pequeñas características o ejemplos de automatización. Algunas cosas clave en la fase de investigación también fueron descubiertas por estudiantes. Aunque a veces es necesario vigilar de cerca la calidad del código. Esto se facilita en gran medida por analizadores estáticos (QuantifiedCode, Codacy y Landscape) y pruebas automáticas en la nube (servicio AppVeyor) con una cobertura de código en torno al 95%.

También agradezco a todos los que dejan comentarios, reportan errores y envían solicitudes de extracción.

Recursos adicionales

Seguimos las preguntas a través de la etiqueta en StackOverflow (recientemente se agregó una etiqueta en la versión rusa de SO) y por palabra clave en Toster. Hay un chat en ruso en Gitter.

Actualizamos cada mes el ranking de bibliotecas open-source para pruebas de GUIEn cuanto al número de estrellas en GitHub, solo Autohotkey (que tiene una gran comunidad y una larga trayectoria) y PyAutoGUI (en gran parte gracias a la popularidad de los libros de su autor Al Sweigart: «Automate the Boring Stuff with Python» y otros) crecen más rápido.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster