Biblioteca de Python — es un proyecto de código abierto para automatizar aplicaciones GUI de escritorio en WindowsEn los últimos dos años, ha añadido importantes novedades:
- Compatibilidad con la tecnología de automatización de la interfaz de usuario de MS. La interfaz sigue siendo la misma y ahora admite WinForms, WPF, Qt5, Windows Tienda (UWP) y demás: casi todo lo que está en Windows.
- Sistema backend/plugin (ahora hay dos de ellos debajo del capó: predeterminado
"win32"y un nuevo"uia"). Luego nos movemos sin problemas hacia el multiplataforma. - Ganchos Win32 para mouse y teclado (teclas de acceso rápido en el espíritu de pyHook).
También haremos una pequeña reseña de lo que está disponible en código abierto para la automatización de escritorio (sin pretensiones de una comparación seria).
Este artículo es una transcripción parcial de un informe de la conferencia SQA Days 20 en Minsk ( и ), versión parcialmente rusa para pywinauto.
- Enfoques clave
- Principales tecnologías de accesibilidad de escritorio
Comencemos con una breve descripción general del código abierto en esta área. Para las aplicaciones GUI de escritorio, las cosas son algo más complicadas que para la web, que tiene Selenium. Estos son los principales enfoques:
método de coordenadas
Puntos de clic de código duro, espero buenos éxitos.
[+] Multiplataforma, fácil de implementar.
[+] Es fácil hacer un registro de pruebas de "grabación-repetición".
[-] Los más inestables ante cambios de resolución de pantalla, tema, tipografías, tamaños de ventana, etc.
[-] Se necesita mucho esfuerzo para el soporte, a menudo es más fácil regenerar pruebas desde cero o probar manualmente.
[-] Solo automatiza acciones, existen otros métodos para verificar y extraer datos.
Herramientas (multiplataforma): , , y muchos otros. Por regla general, las herramientas más complejas incluyen esta funcionalidad (no siempre multiplataforma).
Vale la pena decir que el método de coordenadas puede complementar otros enfoques. Por ejemplo, para gráficos personalizados, puede hacer clic en coordenadas relativas (desde la esquina superior izquierda de la ventana/elemento, y no en toda la pantalla); esto suele ser bastante confiable, especialmente si tiene en cuenta la longitud/anchura de la elemento completo (entonces las diferentes resoluciones de pantalla no le harán daño).
Otra opción es asignar solo una máquina con configuraciones estables para las pruebas (no multiplataforma, pero en algunos casos es bueno).
Reconocimiento de imágenes de referencia
[+] Multiplataforma
[+-] Relativamente confiable (mejor que el método de coordenadas), pero aún requiere trucos.
[-+] Relativamente lento, porque requiere recursos de CPU para algoritmos de reconocimiento.
[-] El reconocimiento de texto (OCR), como regla, está fuera de discusión => no puede obtener datos de texto. Hasta donde yo sé, las soluciones de OCR existentes no son muy fiables para este tipo de tareas, y no se utilizan mucho (bienvenido a los comentarios si no es así).
Herramientas: , (Python puro, compatible con Sikuli), .
tecnología de accesibilidad
[+] El método más fiable, porque le permite buscar texto, independientemente de cómo lo represente el sistema o el marco.
[+] Le permite extraer datos de texto => más fácil de verificar los resultados de las pruebas.
[+] Por regla general, los más rápidos, porque casi no consume recursos de la CPU.
[-] Es difícil crear una herramienta multiplataforma: absolutamente todas las bibliotecas de código abierto admiten una o dos tecnologías de accesibilidad. Windows/LinuxmacOS no cuenta con soporte completo de ninguna plataforma, excepto de aquellas de pago como TestComplete, UFT o Squish.
[-] Dicha tecnología no siempre está disponible en principio. Por ejemplo, probar la pantalla de inicio dentro de VirtualBox es indispensable sin reconocimiento de imágenes. Pero en muchos casos clásicos, el enfoque de accesibilidad sigue siendo aplicable. Sobre esto más y será discutido.
Herramientas: Cª# en C# (compatible con Selenium), en C# (compatible con Appium), , (compatible con LDTP), , en rubí (Linux Proyecto de pruebas de escritorio) y su Windows versión .
LDTP es quizás la única herramienta de código abierto multiplataforma (más precisamente, una familia de bibliotecas) basada en tecnologías de accesibilidad. Sin embargo, él no es muy popular. No lo he usado yo mismo, pero según las revisiones, la interfaz no es la más conveniente. Si hay críticas positivas, compártalas en los comentarios.
Prueba de puerta trasera (también conocida como dentro de la bicicleta)
Para las aplicaciones multiplataforma, los propios desarrolladores a menudo crean un mecanismo interno para garantizar la capacidad de prueba. Por ejemplo, crean un servidor TCP de servicios en la aplicación, las pruebas se conectan a él y envían comandos de texto: en qué hacer clic, de dónde obtener datos, etc. Fiable, pero no universal.
Principales tecnologías de accesibilidad de escritorio
Buena vieja API Win32
Más Windows aplicaciones escritas antes del lanzamiento de WPF y después Windows Las tiendas de aplicaciones están construidas de una forma u otra sobre la API de Win32. Es decir, MFC, WTL, C++ Builder, Delphi, VB6: todas estas herramientas utilizan la API de Win32. Incluso Windows Los formularios son en gran medida compatibles con la API de Win32.
Herramientas: (similar a VB) y envoltorio de Python , (idioma propio, hay una interfaz COM IDispatch), (Pitón), (Rubí), (Rubí).
Automatización de la interfaz de usuario de Microsoft
La principal ventaja: la tecnología MS UI Automation admite la gran mayoría de aplicaciones GUI en Windows Salvo raras excepciones. El problema es que no es mucho más fácil de aprender que la API de Win32. De lo contrario, nadie estaría creando adaptadores para ella.
De hecho, este es un conjunto de interfaces COM personalizadas (principalmente UIAutomationCore.dll) y también tiene un contenedor .NET en la forma namespace System.Windows.Automation. Por cierto, tiene un error introducido, debido al cual se pueden omitir algunos elementos de la interfaz de usuario. Por lo tanto, es mejor usar UIAutomationCore.dll directamente (si escuchó sobre UiaComWrapper en C #, entonces esto es todo).
Variedades de interfaces COM:
(1) La base IU conocida es "la raíz de todos los males". El nivel más bajo, nunca fácil de usar.
(2) IDispatch y derivados (por ejemplo, Excel.Application) que se puede usar en Python usando el paquete win32com.client (incluido con pyWin32). La opción más conveniente y hermosa.
(3) Interfaces personalizadas con las que puede funcionar un paquete Python de terceros .
Herramientas: Cª# 0.6.0 + Cª# (no se revela su código fuente para envoltorios sish sobre UIAutomationCore.dll), en Rubí.
AT-SPI
A pesar de que casi todos los ejes de la familia Linux Basado en el sistema X Window (en Fedora 25, "X" fue reemplazado por Wayland), "X" solo permite operar ventanas de nivel superior y el ratón/teclado. Para un análisis detallado de botones, listas desplegables, etc., se utiliza la tecnología AT-SPI. Los gestores de ventanas más populares cuentan con un demonio de registro AT-SPI, que proporciona una interfaz gráfica de usuario automatizada para las aplicaciones (al menos Qt y GTK son compatibles).
Herramientas: .
pyatspi2, en mi opinión, contiene demasiadas dependencias como el mismo PyGObject. La tecnología en sí está disponible como una biblioteca dinámica regular libatspi.so. Ella tiene Para la biblioteca pywinauto, planeamos implementar la compatibilidad con AT-SPI de esta manera: cargando libatspi.so y el módulo ctypes. El único inconveniente menor es usar la versión correcta, ya que varían ligeramente para las aplicaciones GTK+ y Qt. La versión 0.7.0 de pywinauto probablemente incluirá compatibilidad total. Linux Se prevé que esté disponible en la primera mitad de 2018.
API de accesibilidad de Apple
MacOS tiene su propio lenguaje de automatización, AppleScript. Para implementar algo como esto en Python, por supuesto, necesita usar funciones de ObjectiveC. A partir, al parecer, incluso con MacOS 10.6, el paquete pyobjc está incluido en el python preinstalado. Esto también facilitará la lista de dependencias para soporte futuro en pywinauto.
Herramientas: Además del lenguaje Apple Script, debes prestar atención a , también conocido como piatom. Es una interfaz compatible con LDTP, pero también es una biblioteca independiente. Tiene escrito por mi alumno. Hay un problema conocido: los horarios flexibles no funcionan (métodos waitFor*). Pero, en general, algo bueno.
Cómo empezar con pywinauto
El primer paso es equiparse con un inspector de objetos GUI (lo que se llama la herramienta Spy). Ayudará a estudiar la aplicación desde adentro: cómo se organiza la jerarquía de elementos, qué propiedades están disponibles. Los inspectores de objetos más famosos son:
- Espía ++ - incluido con Visual Studio, incluidas Express o Community Edition. Utiliza la API de Win32. También conocido como clon. Información de la ventana de AutoIt.
- Inspeccionar.exe — está incluido en Windows SDK. Si lo tienes instalado, entonces es de 64 bits. Windows Puedes encontrarlo en la carpeta
C:Program Files (x86)Windows Kits<winver>binx64. En el propio inspector, debe seleccionar el modo Automatización de UI en lugar de MS AA (accesibilidad activa, antepasado de la automatización de la interfaz de usuario).
Habiendo iluminado la aplicación de principio a fin, seleccionamos el backend que usaremos. Es suficiente especificar el nombre del backend al crear el objeto Aplicación.
- back-end="win32" - aunque se usa de forma predeterminada, funciona bien con MFC, WTL, VB6 y otras aplicaciones heredadas.
- backend="uia" — nuevo backend para MS UI Automation: funciona perfectamente con WPF y WinForms; también es bueno para Delphi y Windows Aplicaciones de la tienda; funciona con Qt5 y algunas aplicaciones Java. En general, si Inspect.exe reconoce elementos y sus propiedades, este backend es adecuado. Básicamente, la mayoría de los navegadores también admiten la automatización de la interfaz de usuario (Mozilla de forma predeterminada, y Chrome requiere un parámetro de línea de comandos al iniciarse).
--force-renderer-accessibilitypara ver elementos en páginas en Inspect.exe). Por supuesto, la competencia con Selenium en esta área es casi imposible. Solo otra forma de trabajar con el navegador (puede ser útil para un escenario de productos cruzados).
Puntos de entrada para la automatización
La aplicación ha sido bien investigada. Es hora de crear un objeto Aplicación y ejecutarlo, o adjuntarlo a uno que ya se esté ejecutando. No es solo un clon de la clase estándar. subprocess.Popen, que es un objeto introductorio que limita todas sus acciones a los límites del proceso. Esto es muy útil si se están ejecutando varias instancias de una aplicación y no desea tocar el resto.
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# Опишем окно, которое хотим найти в процессе Notepad.exe
dlg_spec = app.UntitledNotepad
# ждем пока окно реально появится
actionable_dlg = dlg_spec.wait('visible')Si desea administrar varias aplicaciones a la vez, la clase lo ayudará Desktop. Por ejemplo, en la calculadora de Win10, la jerarquía de elementos se distribuye en varios procesos (no solo calc.exe). Entonces ningún objeto Desktop no es suficiente
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')Objeto raíz (Application o Desktop) es el único lugar donde debe especificar el backend. Todo lo demás cae transparentemente en el concepto de "especificación-> contenedor", que se discutirá más adelante.
Especificaciones de ventana/elemento
Este es el concepto central sobre el que se construye la interfaz de pywinauto. Puede describir la ventana/elemento de forma aproximada o con más detalle, incluso si aún no existe o ya está cerrado. especificación de ventana (objeto especificación de la ventana) almacena los criterios por los que necesita buscar una ventana o elemento real.
Un ejemplo de una especificación de ventana detallada:
>>> dlg_spec = app.window(title='Untitled - Notepad')
>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>
>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>La búsqueda de la ventana en sí se produce llamando al método .wrapper_object(). Devuelve algún "envoltorio" para una ventana/elemento real o lanza ElementNotFoundError (algunas veces ElementAmbiguousError, si se encuentran varios elementos, es decir, debe refinar los criterios de búsqueda). Este "envoltorio" ya sabe cómo hacer algunas acciones con el elemento o recibir datos de él.
Python puede ocultar la llamada .wrapper_object(), para que el código final sea más corto. Recomendamos usarlo solo con fines de depuración. Las siguientes dos líneas hacen exactamente lo mismo:
dlg_spec.wrapper_object().minimize() # debugging
dlg_spec.minimize() # productionHay una variedad de criterios de búsqueda para una especificación de ventana. Estos son 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')La lista de todos los criterios posibles está en los muelles de funciones. .
La magia del acceso por atributo y por clave
Python facilita la creación de especificaciones de ventana y el reconocimiento de atributos de objetos dinámicamente (anulados internamente __getattribute__). Por supuesto, se imponen las mismas restricciones sobre el nombre del atributo que sobre el nombre de cualquier variable (no se pueden insertar espacios, comas y otros caracteres especiales). Afortunadamente, pywinauto utiliza un algoritmo de búsqueda llamado "mejor coincidencia" que es resistente a errores tipográficos y ligeras variaciones.
app.UntitledNotepad
# то же самое, что
app.window(best_match='UntitledNotepad')Si aún necesita cadenas Unicode (por ejemplo, para el idioma ruso), espacios, etc., puede acceder por clave (como si fuera un diccionario normal):
app['Untitled - Notepad']
# то же самое, что
app.window(best_match='Untitled - Notepad')Cinco reglas para los nombres mágicos
¿Cómo averiguar los nombres mágicos de referencia? Las que se asignan al elemento antes de la búsqueda. Si ha especificado un nombre que es lo suficientemente similar a la plantilla, se encontrará el elemento.
- Por título (texto, nombre):
app.Properties.OK.click() - Por texto y por tipo de elemento:
app.Properties.OKButton.click() - Por tipo y por número:
app.Properties.Button3.click()(nombresButton0иButton1ligado al primer elemento encontrado,Button2- al segundo, y luego en orden - sucedió históricamente) - Por texto estático (izquierda o arriba) y por tipo:
app.OpenDialog.FileNameEdit.set_text("")(útil para elementos con texto dinámico) - Por tipo y por texto interior:
app.Properties.TabControlSharing.select("General")
Por lo general, se aplican dos o tres reglas al mismo tiempo, rara vez más. Para verificar qué nombres específicos están disponibles para cada elemento, puede usar el método imprimir_control_identificadores(). Puede imprimir un árbol de elementos tanto en la pantalla como en un archivo. Para cada elemento, se imprimen sus nombres mágicos de referencia. También puede copiar y pegar especificaciones más detalladas de elementos secundarios desde allí. El resultado en el script se verá así:
app.Properties.child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")El árbol de elementos en sí suele ser un pie bastante 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13080", control_type="Text")
|
| Edit - 'Type:' (L790, T643, R1036, B666)
| [u'4', 'Edit2', u'Type:Edit']
| child_window(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13059", control_type="Edit")
|
| Static - 'Location:' (L717, T669, R780, B684)
| [u'Location:Static', u'Location:', u'Static2']
| child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13089", control_type="Text")
|
| Edit - 'Location:' (L790, T669, R1036, B692)
| ['Edit3', u'Location:Edit', u'5']
| child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13065", control_type="Edit")
|
| Static - 'Size:' (L717, T695, R780, B710)
| [u'Size:Static', u'Size:', u'Static3']
| child_window(data-gt-translate-attributes='["title"]' title="Size:", auto_id="13081", control_type="Text")
|
| Edit - 'Size:' (L790, T695, R1036, B718)
| ['Edit4', u'6', u'Size:Edit']
| child_window(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13088", control_type="Text")
|
| Edit - 'Contains:' (L790, T747, R1036, B770)
| [u'8', 'Edit6', u'Contains:Edit']
| child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")
|
| Image - 'Contains:' (L717, T773, R1035, B775)
| [u'Contains:Image', 'Image3', u'Contains:2']
| child_window(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13092", control_type="Text")
|
| Edit - 'Created:' (L790, T786, R1036, B809)
| [u'Created:Edit', 'Edit7', u'9']
| child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13072", control_type="Edit")
|
| Image - 'Created:' (L717, T812, R1035, B814)
| [u'Created:Image', 'Image4', u'Created:2']
| child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13097", control_type="Image")
|
| Static - 'Attributes:' (L717, T825, R780, B840)
| [u'Attributes:Static', u'Static7', u'Attributes:']
| child_window(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="Advanced...", auto_id="13154", control_type="Button")
|
| Button - 'OK' (L814, T968, R889, B991)
| ['Button2', u'OK', u'OKButton']
| child_window(data-gt-translate-attributes='["title"]' title="OK", auto_id="1", control_type="Button")
|
| Button - 'Cancel' (L895, T968, R970, B991)
| ['Button3', u'CancelButton', u'Cancel']
| child_window(data-gt-translate-attributes='["title"]' title="Cancel", auto_id="2", control_type="Button")
|
| Button - 'Apply' (L976, T968, R1051, B991)
| ['Button4', u'ApplyButton', u'Apply']
| child_window(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="General", control_type="TabItem")
| |
| | TabItem - 'Sharing' (L753, T558, R801, B576)
| | [u'Sharing', u'SharingTabItem', 'TabItem2']
| | child_window(data-gt-translate-attributes='["title"]' title="Sharing", control_type="TabItem")
| |
| | TabItem - 'Security' (L801, T558, R851, B576)
| | [u'Security', 'TabItem3', u'SecurityTabItem']
| | child_window(data-gt-translate-attributes='["title"]' title="Security", control_type="TabItem")
| |
| | TabItem - 'Previous Versions' (L851, T558, R947, B576)
| | [u'Previous VersionsTabItem', u'Previous Versions', 'TabItem4']
| | child_window(data-gt-translate-attributes='["title"]' title="Previous Versions", control_type="TabItem")
| |
| | TabItem - 'Customize' (L947, T558, R1007, B576)
| | [u'CustomizeTabItem', 'TabItem5', u'Customize']
| | child_window(data-gt-translate-attributes='["title"]' 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(data-gt-translate-attributes='["title"]' title="System", auto_id="MenuBar", control_type="MenuBar")
| | |
| | | MenuItem - 'System' (L696, T526, R718, B548)
| | | [u'System2', u'MenuItem', u'SystemMenuItem']
| | | child_window(data-gt-translate-attributes='["title"]' title="System", control_type="MenuItem")
| |
| | Button - 'Close' (L1024, T519, R1058, B549)
| | [u'CloseButton', u'Close', 'Button5']
| | child_window(data-gt-translate-attributes='["title"]' title="Close", control_type="Button")En algunos casos, la impresión de todo el árbol puede ralentizarse (por ejemplo, ¡en iTunes hay hasta tres mil elementos en una pestaña!), Pero puede usar el parámetro depth (profundidad): depth=1 - el elemento mismo depth=2 — solo hijos inmediatos, y así sucesivamente. También se puede especificar en las especificaciones al crear child_window.
Примеры
Estamos constantemente reponiendo . De los nuevos, cabe destacar la automatización del analizador de red WireShark (este es un buen ejemplo de una aplicación Qt5; aunque esta tarea se puede resolver sin una GUI, porque hay scapy.Sniffer del paquete python ). También hay un ejemplo de automatización de MS Paint con su barra de herramientas Ribbon.
Otro gran ejemplo escrito por mi estudiante: (migrará al repositorio principal un poco más tarde).
Y, por supuesto, un ejemplo de suscripción a eventos de teclado (teclas de acceso rápido) y mouse:
.
Agradecimientos
Un agradecimiento especial a quienes ayudan constantemente a desarrollar el proyecto. para mi y es un pasatiempo continuo. Dos de mis alumnos de la UNN terminaron recientemente su licenciatura en este tema. hizo una gran contribución al soporte de MS UI Automation y recientemente comenzó a crear un generador de código automático basado en el principio de "reproducción de registros" basado en propiedades de texto (esta es la característica más difícil), hasta ahora solo para el backend "uia". está desarrollando un nuevo backend para Linux basado en AT-SPI (módulos mouse и keyboard basado en - ya en las versiones 0.6.x).
Como llevo bastante tiempo impartiendo un curso especial sobre automatización en Python, algunos de los alumnos del máster hacen sus deberes, implementando pequeñas funcionalidades o ejemplos de automatización. Los estudiantes también descubrieron algunas cosas clave en la etapa de investigación. Aunque a veces hay que vigilar estrictamente la calidad del código. A esto ayudan mucho los analizadores estáticos (QuantifiedCode, Codacy y Landscape) y las pruebas automatizadas en la nube (servicio AppVeyor) con una cobertura de código de alrededor del 95%.
¡También gracias a todos los que dejan comentarios, inician errores y envían solicitudes de extracción!
Recursos adicionales
Seguimos preguntas sobre (recientemente aparecido ) Y . Hay .
Actualizamos cada mes . En cuanto a la cantidad de estrellas en github, solo Autohotkey (tienen una comunidad muy grande y una larga historia) y PyAutoGUI (en gran parte debido a la popularidad de los libros de su autor Al Sweigart: "Automate the Boring Stuff with Python" y otros) están creciendo más rápido.
Fuente: habr.com
