pywinauto
pywinauto is a set of python modules to automate the Microsoft Windows GUI.
At its simplest it allows you to send mouse and keyboard actions to windows
dialogs and controls, but it has support for more complex actions like getting text data.
Supported technologies under the hood: Win32 API (backend="win32"
; used by default),
MS UI Automation (backend="uia"
). User input emulation modules
mouse
and keyboard
work on both Windows and Linux.
Enjoying this?
Just star the repo or make a donation.
Your help is valuable since this is a hobby project for all of us: we do
new features development during out-of-office hours.
- In general the library tends to be cross-platform in the near future (Linux in 2018, macOS in 2019).
- Reliable text based «record-replay» generator is also a high priority feature under development.
- More feature requests and discusions are welcome in the issues.
Setup
pip
Execute pip install -U pywinauto
conda
Execute conda install -c conda-forge pywinauto
Documentation / Help
- Short Intro on ReadTheDocs
- Getting Started Guide (core concept, Spy/Inspect tools etc.)
- StackOverflow tag for questions
- Mailing list
Simple Example
It is simple and the resulting scripts are very readable. How simple?
from pywinauto.application import Application app = Application().start("notepad.exe") app.UntitledNotepad.menu_select("Help->About Notepad") app.AboutNotepad.OK.click() app.UntitledNotepad.Edit.type_keys("pywinauto Works!", with_spaces = True)
MS UI Automation Example
More detailed example for explorer.exe
:
from pywinauto import Desktop, Application Application().start('explorer.exe "C:\\Program Files"') # connect to another process spawned by explorer.exe # Note: make sure the script is running as Administrator! app = Application(backend="uia").connect(path="explorer.exe", title="Program Files") app.ProgramFiles.set_focus() common_files = app.ProgramFiles.ItemsView.get_item('Common Files') common_files.right_click_input() app.ContextMenu.Properties.invoke() # this dialog is open in another process (Desktop object doesn't rely on any process id) Properties = Desktop(backend='uia').Common_Files_Properties Properties.print_control_identifiers() Properties.Cancel.click() Properties.wait_not('visible') # make sure the dialog is closed
Dependencies (if install manually)
- Windows:
- pyWin32
- comtypes
- Linux:
- python-xlib
- Optional packages:
- Install Pillow (by
pip install -U Pillow
) to be able to callcapture_as_image()
method for making a control’s snapshot.
- Install Pillow (by
Packages required for running unit tests
- Pillow
- coverage
Run all the tests: python ./pywinauto/unittests/testall.py
Contribution
Pull requests are very welcome. Read Contribution Guide for more details about unit tests, coding conventions, etc.
Copyrights
Pywinauto for native Windows GUI was initially written by Mark Mc Mahon.
Mark brought many great ideas into the life using power of Python.
Further contributors are inspired of the nice API so that the development continues.
Starting from 0.6.0 pywinauto is distributed under the BSD 3-clause license.
Pywinauto 0.5.4 and before was distributed under the LGPL v2.1 or later.
- (c) The Open Source Community, 2015-2018 (0.6.0+ development)
- (c) Intel Corporation, 2015 (0.5.x maintenance)
- (c) Michael Herrmann, 2012-2013 (0.4.2)
- (c) Mark Mc Mahon, 2006-2010 (0.4.0 and before)
Время на прочтение14 мин
Количество просмотров106K
Python библиотека pywinauto — это open source проект по автоматизации десктопных GUI приложений на Windows. За последние два года в ней появились новые крупные фичи:
- Поддержка технологии MS UI Automation. Интерфейс прежний, и теперь поддерживаются: WinForms, WPF, Qt5, Windows Store (UWP) и так далее — почти все, что есть на Windows.
- Система бэкендов/плагинов (сейчас их двое под капотом: дефолтный
"win32"
и новый"uia"
). Дальше плавно двигаемся в сторону кросс-платформенности. - Win32 хуки для мыши и клавиатуры (hot keys в духе pyHook).
Также сделаем небольшой обзор того, что есть в open source для десктопной автоматизации (без претензий на серьезное сравнение).
Эта статья — частично расшифровка доклада с конференции SQA Days 20 в Минске (видеозапись и слайды), частично русская версия Getting Started Guide для pywinauto.
- Основные подходы
- Координатный метод
- Распознавание эталонных изображений
- Accessibility технологии
- Основные десктопные accessibility технологии
- Старый добрый Win32 API
- Microsoft UI Automation
- AT-SPI (Linux)
- Apple Accessibility API
- Как начать работать с pywinauto
- Входные точки для автоматизации
- Спецификации окон/элементов
- Магия доступа по атрибуту и по ключу
- Пять правил для магических имен
Начнём с краткого обзора опен сорса в этой области. Для десктопных GUI приложений всё несколько сложнее, чем для веба, у которого есть Selenium. Вот основные подходы:
Координатный метод
Хардкодим точки кликов, надеемся на удачные попадания.
[+] Кросс-платформенный, легко реализуемый.
[+] Легко сделать «record-replay» запись тестов.
[-] Самый нестабильный к изменению разрешения экрана, темы, шрифтов, размеров окон и т.п.
[-] Нужны огромные усилия на поддержку, часто проще перегенерить тесты с нуля или тестировать вручную.
[-] Автоматизирует только действия, для верификации и извлечения данных есть другие методы.
Инструменты (кросс-платформенные): autopy, PyAutoGUI, PyUserInput и многие другие. Как правило, более сложные инструменты включают в себя эту функциональность (не всегда кросс-платформенно).
Стоит сказать, что координатный метод может дополнять остальные подходы. Например, для кастомной графики можно кликать по относительным координатам (от левого верхнего угла окна/элемента, а не всего экрана) — обычно это достаточно надежно, особенно если учитывать длину/ширину всего элемента (тогда и разное разрешение экрана не помешает).
Другой вариант: выделять для тестов только одну машину со стабильными настройками (не кросс-платформенно, но в каких-то случаях годится).
Распознавание эталонных изображений
[+] Кросс-платформенный
[+-] Относительно надежный (лучше, чем координатный метод), но всё же требует хитростей.
[-+] Относительно медленный, т.к. требует ресурсов CPU для алгоритмов распознавания.
[-] О распознавании текста (OCR), как правило, речи не идёт => нельзя достать текстовые данные. Насколько мне известно, существующие OCR решения не слишком надежны для этого типа задач, и широкого применения не имеют (welcome в комменты, если это уже не так).
Инструменты: Sikuli, Lackey (Sikuli-совместимый, на чистом Python), PyAutoGUI.
Accessibility технологии
[+] Самый надежный метод, т.к. позволяет искать по тексту, независимо от того, как он отрисован системой или фреймворком.
[+] Позволяет извлекать текстовые данные => проще верифицировать результаты тестов.
[+] Как правило, самый быстрый, т.к. почти не расходует ресурсы CPU.
[-] Тяжело сделать кросс-платформенный инструмент: абсолютно все open-source библиотеки поддерживают одну-две accessibility технологии. Windows/Linux/MacOS целиком не поддерживает никто, кроме платных типа TestComplete, UFT или Squish.
[-] Не всегда такая технология в принципе доступна. Например, тестирование загрузочного экрана внутри VirtualBox’а — тут без распознавания изображений не обойтись. Но во многих классических случаях все-таки accessibility подход применим. О нем дальше и пойдет речь.
Инструменты: TestStack.White на C#, Winium.Desktop на C# (Selenium совместимый), MS WinAppDriver на C# (Appium совместимый), pywinauto, pyatom (совместим с LDTP), Python-UIAutomation-for-Windows, RAutomation на Ruby, LDTP (Linux Desktop Testing Project) и его Windows версия Cobra.
LDTP — пожалуй, единственный кросс-платформенный open-source инструмент (точнее семейство библиотек) на основе accessibility технологий. Однако он не слишком популярен. Сам не пользовался им, но по отзывам интерфейс у него не самый удобный. Если есть позитивные отзывы, прошу поделиться в комментах.
Тестовый backdoor (a.k.a. внутренний велосипед)
Для кросс-платформенных приложений сами разработчики часто делают внутренний механизм для обеспечения testability. Например, создают служебный TCP сервер в приложении, тесты к нему подключаются и посылают текстовые команды: на что нажать, откуда взять данные и т.п. Надежно, но не универсально.
Основные десктопные accessibility технологии
Старый добрый Win32 API
Большинство Windows приложений, написанных до выхода WPF и затем Windows Store, построены так или иначе на Win32 API. А именно, MFC, WTL, C++ Builder, Delphi, VB6 — все эти инструменты используют Win32 API. Даже Windows Forms — в значительной степени Win32 API совместимые.
Инструменты: AutoIt (похож на VB) и Python обертка pyautoit, AutoHotkey (собственный язык, есть IDispatch COM интерфейс), pywinauto (Python), RAutomation (Ruby), win32-autogui (Ruby).
Microsoft UI Automation
Главный плюс: технология MS UI Automation поддерживает подавляющее большинство GUI приложений на Windows за редкими исключениями. Проблема: она не сильно легче в изучении, чем Win32 API. Иначе никто бы не делал оберток над ней.
Фактически это набор custom COM интерфейсов (в основном, UIAutomationCore.dll), а также имеет .NET оболочку в виде namespace System.Windows.Automation
. Она, кстати, имеет привнесенный баг, из-за которого некоторые UI элементы могут быть пропущены. Поэтому лучше использовать UIAutomationCore.dll напрямую (если слышали про UiaComWrapper на C#, то это оно).
Разновидности COM интерфейсов:
(1) Базовый IUknown — «the root of all evil». Самый низкоуровневый, ни разу не user-friendly.
(2) IDispatch и производные (например, Excel.Application
), которые можно использовать в Python с помощью пакета win32com.client (входит в pyWin32). Самый удобный и красивый вариант.
(3) Custom интерфейсы, с которыми умеет работать сторонний Python пакет comtypes.
Инструменты: TestStack.White на C#, pywinauto 0.6.0+, Winium.Desktop на C#, Python-UIAutomation-for-Windows (у них исходный код сишных оберток над UIAutomationCore.dll не раскрыт), RAutomation на Ruby.
AT-SPI
Несмотря на то, что почти все оси семейства Linux построены на X Window System (в Fedora 25 «иксы» поменяли на Wayland), «иксы» позволяют оперировать только окнами верхнего уровня и мышью/клавиатурой. Для детального разбора по кнопкам, лист боксам и так далее — существует технология AT-SPI. У самых популярных оконных менеджеров есть так называемый AT-SPI registry демон, который и обеспечивает для приложений автоматизируемый GUI (как минимум поддерживаются Qt и GTK).
Инструменты: pyatspi2.
pyatspi2, на мой взгляд, содержит слишком много зависимостей типа того же PyGObject. Сама технология доступна в виде обычной динамической библиотеки libatspi.so
. К ней имеется Reference Manual. Для библиотеки pywinauto планируем реализовать поддержку AT-SPI имеено так: через загрузку libatspi.so и модуль ctypes. Есть небольшая проблема только в использовании нужной версии, ведь для GTK+ и Qt приложений они немного разные. Вероятный выпуск pywinauto 0.7.0 с полноценной поддержкой Linux можно ожидать в первой половине 2018-го.
Apple Accessibility API
На MacOS есть собственный язык автоматизации AppleScript. Для реализации чего-то подобного на Python, разумеется, нужно использовать функции из ObjectiveC. Начиная, кажется, еще с MacOS 10.6 в предустановленный питон включается пакет pyobjc. Это также облегчит список зависимостей для будущей поддержки в pywinauto.
Инструменты: Кроме языка Apple Script, стоит обратить внимание на ATOMac, он же pyatom. Он совместим по интерфейсу с LDTP, но также является самостоятельной библиотекой. На нем есть пример автоматизации iTunes на macOs, написанный моим студентом. Есть известная проблема: не работают гибкие тайминги (методы waitFor*
). Но, в целом, неплохая вещь.
Как начать работать с pywinauto
Первым делом стоит вооружиться инспектором GUI объектов (то, что называют Spy tool). Он поможет изучить приложение изнутри: как устроена иерархия элементов, какие свойства доступны. Самые известные инспекторы объектов:
- Spy++ — входит в поставку Visual Studio, включая Express или Community Edition. Использует Win32 API. Также известен его клон AutoIt Window Info.
- Inspect.exe — входит в Windows SDK. Если он у вас установлен, то на 64-битной Windows можно найти его в папке
C:\Program Files (x86)\Windows Kits\<winver>\bin\x64
. В самом инспекторе нужно выбрать режим UI Automation вместо MS AA (Active Accessibility, предок UI Automation).
Просветив приложение насквозь, выбираем бэкенд, который будем использовать. Достаточно указать имя бэкенда при создании объекта Application.
- backend=»win32″ — пока используется по умолчанию, хорошо работает с MFC, WTL, VB6 и другими legacy приложениями.
- backend=»uia» — новый бэкенд для MS UI Automation: идеально работает с WPF и WinForms; также хорош для Delphi и Windows Store приложений; работает с Qt5 и некоторыми Java приложениями. И вообще, если Inspect.exe видит элементы и их свойства, значит этот бэкенд подходит. В принципе, большинство браузеров тоже поддерживает UI Automation (Mozilla по умолчанию, а Хрому при запуске нужно скормить ключ командной строки
--force-renderer-accessibility
, чтобы увидеть элементы на страницах в Inspect.exe). Конечно, конкуренция с Selenium в этой области навряд ли возможна. Просто еще один способ работать с браузером (может пригодиться для кросс-продуктового сценария).
Входные точки для автоматизации
Приложение достаточно изучено. Пора создать объект Application и запустить его или присоединиться к уже запущенному. Это не просто клон стандартного класса subprocess.Popen
, а именно вводный объект, который ограничивает все ваши действия границами процесса. Это очень полезно, если запущено несколько экземпляров приложения, а остальные трогать не хочется.
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# Опишем окно, которое хотим найти в процессе Notepad.exe
dlg_spec = app.UntitledNotepad
# ждем пока окно реально появится
actionable_dlg = dlg_spec.wait('visible')
Если хочется управлять сразу несколькими приложениями, вам поможет класс Desktop
. Например, в калькуляторе на Win10 иерархия элементов размазана аж по нескольким процессам (не только calc.exe
). Так что без объекта Desktop
не обойтись.
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')
Корневой объект (Application
или Desktop
) — это единственное место, где нужно указывать бэкенд. Все остальное прозрачно ложится в концепцию «спецификация->враппер», о которой дальше.
Спецификации окон/элементов
Это основная концепция, на которой строится интерфейс pywinauto. Вы можете описать окно/элемент приближенно или более детально, даже если оно еще не существует или уже закрыто. Спецификация окна (объект WindowSpecification) хранит в себе критерии, по которым нужно искать реальное окно или элемент.
Пример детальной спецификации окна:
>>> 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>
Сам поиск окна происходит по вызову метода .wrapper_object()
. Он возвращает некий «враппер» для реального окна/элемента или кидает ElementNotFoundError
(иногда ElementAmbiguousError
, если найдено несколько элементов, то есть требуется уточнить критерий поиска). Этот «враппер» уже умеет делать какие-то действия с элементом или получать данные из него.
Python может скрывать вызов .wrapper_object()
, так что финальный код становится короче. Рекомендуем использовать его только для отладки. Следующие две строки делают абсолютно одно и то же:
dlg_spec.wrapper_object().minimize() # debugging
dlg_spec.minimize() # production
Есть множество критериев поиска для спецификации окна. Вот лишь несколько примеров:
# могут иметь несколько уровней
app.window(title_re='.* - Notepad$').window(class_name='Edit')
# можно комбинировать критерии (как AND) и не ограничиваться одним процессом приложения
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')
Список всех возможных критериев есть в доках функции pywinauto.findwindows.find_elements(…).
Магия доступа по атрибуту и по ключу
Python упрощает создание спецификаций окна и распознает атрибуты объекта динамически (внутри переопределен метод __getattribute__
). Разумеется, на имя атрибута накладываются такие же ограничения, как и на имя любой переменной (нельзя вставлять пробелы, запятые и прочие спецсимволы). К счастью, pywinauto использует так называемый «best match» алгоритм поиска, который устойчив к опечаткам и небольшим вариациям.
app.UntitledNotepad
# то же самое, что
app.window(best_match='UntitledNotepad')
Если все-таки нужны Unicode строки (например, для русского языка), пробелы и т.п., можно делать доступ по ключу (как будто это обычный словарь):
app['Untitled - Notepad']
# то же самое, что
app.window(best_match='Untitled - Notepad')
Пять правил для магических имен
Как узнать эталонные магические имена? Те, которые присваиваются элементу перед поиском. Если вы указали имя, достаточно похожее на эталон, значит элемент будет найден.
- По заголовку (текст, имя):
app.Properties.OK.click()
- По тексту и по типу элемента:
app.Properties.OKButton.click()
- По типу и по номеру:
app.Properties.Button3.click()
(именаButton0
иButton1
привязаны к первому найденному элементу,Button2
— ко второму, и дальше уже по порядку — так исторически сложилось) - По статическому тексту (слева или сверху) и по типу:
app.OpenDialog.FileNameEdit.set_text("")
(полезно для элементов с динамическим текстом) - По типу и по тексту внутри:
app.Properties.TabControlSharing.select("General")
Обычно два-три правила применяются одновременно, редко больше. Чтобы проверить, какие конкретно имена доступны для каждого элемента, можно использовать метод print_control_identifiers(). Он может печатать дерево элементов как на экран, так и в файл. Для каждого элемента печатаются его эталонные магические имена. Также можно скопипастить оттуда более детальные спецификации дочерних элементов. Результат в скрипте будет выглядеть так:
app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")
Само дерево элементов — обычно довольно большая портянка.
>>> 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")
В некоторых случаях печать всего дерева может тормозить (например, в iTunes на одной вкладке аж три тысячи элементов!), но можно использовать параметр depth
(глубина): depth=1
— сам элемент, depth=2
— только непосредственные дети, и так далее. Его же можно указывать в спецификациях при создании child_window
.
Примеры
Мы постоянно пополняем список примеров в репозитории. Из свежих стоит отметить автоматизацию сетевого анализатора WireShark (это хороший пример Qt5 приложения; хотя эту задачу можно решать и без GUI, ведь есть scapy.Sniffer
из питоновского пакета scapy). Также есть пример автоматизации MS Paint с его Ribbon тулбаром.
Еще один отличный пример, написанный моим студентом: перетаскивание файла из explorer.exe на Chrome страницу для Google Drive (он перекочует в главный репозиторий чуть позже).
И, конечно, пример подписки на события клавиатуры (hot keys) и мыши:
hook_and_listen.py.
Благодарности
Отдельное спасибо — тем, кто постоянно помогает развивать проект. Для меня и Валентина это постоянное хобби. Двое моих студентов из ННГУ недавно защитили дипломы бакалавра по этой теме. Александр внес большой вклад в поддержку MS UI Automation и недавно начал делать автоматический генератор кода по принципу «запись-воспроизведение» на основе текстовых свойств (это самая сложная фича), пока только для «uia» бэкенда. Иван разрабатывает новый бэкенд под Linux на основе AT-SPI (модули mouse
и keyboard
на основе python-xlib — уже в релизах 0.6.x).
Поскольку я довольно давно читаю спецкурс по автоматизации на Python, часть студентов-магистров выполняют домашние задания, реализуя небольшие фичи или примеры автоматизации. Некоторые ключевые вещи на стадии исследований тоже когда-то раскопали именно студенты. Хотя иногда за качеством кода приходится строго следить. В этом сильно помогают статические анализаторы (QuantifiedCode, Codacy и Landscape) и автоматические тесты в облаке (сервис AppVeyor) с покрытием кода в районе 95%.
Также спасибо всем, кто оставляет отзывы, заводит баги и присылает пулл реквесты!
Дополнительные ресурсы
За вопросами мы следим по тегу на StackOverflow (недавно появился тег в русской версии SO) и по ключевому слову на Тостере. Есть русскоязычный чат в Gitter’е.
Каждый месяц обновляем рейтинг open-source библиотек для GUI тестирования. По количеству звезд на гитхабе быстрее растут только Autohotkey (у них очень большое сообщество и длинная история) и PyAutoGUI (во многом благодаря популярности книг ее автора Al Sweigart: «Automate the Boring Stuff with Python» и других).
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
62 331
01 мая 2020 в 16:46
Питон славиться наличием большого количества очень крутых библиотек. В статье мы рассмотрим работу с библиотекой PyAutoGui, которая позволяет манипулировать компьютером.
Первое что необходимо сделать — это установить всё необходимое. Нам потребуется установить Python с официального сайта, а также скачать IDE, можно использовать PyCharm и далее создать внутри него новый проект.
Установка библиотеки
Теперь необходимо выполнить установку всех библиотек. Если вы работаете на Windows, то вам повезло и все что нужно — команду
pip install pyautogui
.
Если же вы работаете на Mac OS, то лучше вначале выполнить установку
pip install pyobjc
, сразу же за ней выполнить установку
pip install pyobjc-core
и только потом выполнить установку
pip install pyautogui
.
Работа с библиотекой
Используя библиотеку вы можете работать с мышью: вводить ею, нажимать, перетаскивать и так далее, а также можете работать с клавиатурой: вводить текст, нажимать на клавиши, выполнять нажатие на связку команд.
Некоторые из команд представлены ниже вместе с описанием:
import pyautogui as pg
# Получение позиции мыши и вывод в консоль
print(pg.position())
# Передвижение мыши
pg.move(50, 50, duration=0.5)
pg.moveTo(150, 200, 0.5) # Передвигаем к точке относительно экрана
# Нажатие мышкой по определенной точке
pg.click(769, 101)
pg.doubleclick(769, 101) # двойное нажатие
pg.rightclick(769, 101) # нажатие правой кнопкной мыши
pg.leftclick(769, 101) # нажатие левой кнопкной мыши
# Ввод текста
pg.typewrite("itproger.com")
# Выполнения нажатия на клавишу
pg.typewrite(["enter"])
# Выполнения нажатия на сочетание клавиш
pg.hotkey("winleft")
pg.hotkey("winleft", "up")
pg.hotkey("ctrl", "t")
# Вызов различных всплывающих окон
pg.alert("Окно с информацией", "Название окна", button="Текст на кнопке")
age = pg.prompt("Укажите возраст: ", "Название окна")
print(age)
pg.confirm("Вам больше 18?", "Название окна", ("Да, точно", "Нет"))
pg.password("Введите пароль", "Название окна")
# Создание скриншота
pg.screenshot("yourPic.png")
# Мини программа
website = pg.prompt("Введите название сайта:", "Веб сайт", "https://")
pg.click(769, 101)
pg.typewrite(website)
pg.typewrite(["enter"])
pg.screenshot("yourPic.png")
Видео на эту тему
Также вы можете просмотреть детальное видео по работе с библиотекой:
Дополнительный курс
На нашем сайте также есть углубленный курс по изучению языка Питон. В ходе огромной программы вы изучите не только язык Питон, но также научитесь создавать веб сайты за счёт веб технологий и фреймворка Джанго. За курс вы изучите массу нового и к концу программы будете уметь работать с языком Питон, создавать на нём полноценные ПК приложения на основе библиотеки Kivy, а также создавать веб сайты на основе библиотеки Джанго.
Больше интересных новостей
Project description
Download files
Download the file for your platform. If you’re not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file pywinauto-0.6.9.tar.gz
.
File metadata
-
Download URL:
pywinauto-0.6.9.tar.gz - Upload date:
- Size: 434.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.15.0 pkginfo/1.8.3 requests/2.27.1 setuptools/41.2.0 requests-toolbelt/1.0.0 tqdm/4.64.1 CPython/2.7.18
File hashes
Hashes for pywinauto-0.6.9.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 94d710bfa796df245250f952ffa65d97233d9807bcd42e052b71b81af469b6de |
|
MD5 | 76cc6afe77a0f39b51330e9fd2149d25 |
|
BLAKE2b-256 | 2c85cc65e3b64e7473cc86c07f0b5c415d509402c03ef19dadc39c583835eb5f |
See more details on using hashes here.
File details
Details for the file pywinauto-0.6.9-py2.py3-none-any.whl
.
File metadata
-
Download URL:
pywinauto-0.6.9-py2.py3-none-any.whl - Upload date:
- Size: 363.0 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.15.0 pkginfo/1.8.3 requests/2.27.1 setuptools/41.2.0 requests-toolbelt/1.0.0 tqdm/4.64.1 CPython/2.7.18
File hashes
Hashes for pywinauto-0.6.9-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5924b3072864a1d730c5546bbeb17cf4063ba518b618dbc5e43c18276c7c9356 |
|
MD5 | a6f6b43f0076e893b6e0bcc1bea749bf |
|
BLAKE2b-256 | 4446f2283648e0c237af451dc50ebc42121abfd198d12391fd9ad4df1c6b97d2 |
See more details on using hashes here.
Введение | |
Установка | |
Пример применения | |
print_control_identifiers() | |
Объекты во вложенных меню | |
Пример поиска вложенного объекта | |
Поиск по ctrl_ids и ввод текста | |
Поиск по app.windows() | |
requirements.txt | |
Common Files | |
Похожие статьи |
Введение
pywinauto — это набор модулей
python
для автоматизации графического интерфейса
Microsoft Windows
.
В самом простом варианте он позволяет отправлять действия мыши и клавиатуры в диалоговые окна и элементы управления Windows.
Официальная документация
В статье пока не хватает непосредственно тестирования — то есть проверки каких-то сценариев, я планирую добавить обертку на
pytest или robot, но пока не дошли руки.
Установка
python -m pip install pywinauto
Collecting pywinauto
Downloading pywinauto-0.6.8-py2.py3-none-any.whl (362 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 362.9/362.9 kB ? eta 0:00:00
Collecting six
Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting comtypes
Downloading comtypes-1.2.0-py2.py3-none-any.whl (184 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 184.3/184.3 kB ? eta 0:00:00
Collecting pywin32
Downloading pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.2/9.2 MB 21.8 MB/s eta 0:00:00
Installing collected packages: pywin32, comtypes, six, pywinauto
Successfully installed comtypes-1.2.0 pywin32-306 pywinauto-0.6.8 six-1.16.0
Пример
В некоторых случаях нужно перейти в директорию с .exe файлом перед тем как запускать приложение
import os
os.chdir(«PATH»)
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
# app = Application(backend=’win32′).start(»)
python pywinauto_tutorial.py
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(title=‘Untitled — Notepad’, timeout=100)
print_control_identifiers()
Чтобы управлять приложением, нужно получить список объектов — контроллеров. Сделать это
можно с помощью
print_control_identifiers()
print_control_identifiers() выводит на экран список всех контроллеров доступных из
текущего состояния приложения а не вообще все контроллеры.
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(title=‘Untitled — Notepad’, timeout=100)
app.UntitledNotepad.print_control_identifiers()
Control Identifiers:
Dialog — ‘Untitled — Notepad’ (L-1127, T248, R-579, B1034)
[‘Untitled — Notepad’, ‘Untitled — NotepadDialog’, ‘Dialog’]
child_window(title=»Untitled — Notepad», control_type=»Window»)
|
| Edit — ‘Text Editor’ (L-1118, T311, R-588, B996)
| [‘Edit’, ‘Edit0’, ‘Edit1’]
| child_window(title=»Text Editor», auto_id=»15″, control_type=»Edit»)
| |
| | ScrollBar — ‘Vertical’ (L-609, T311, R-588, B975)
| | [‘VerticalScrollBar’, ‘Vertical’, ‘ScrollBar’, ‘ScrollBar0’, ‘ScrollBar1’]
| | child_window(title=»Vertical», auto_id=»NonClientVerticalScrollBar», control_type=»ScrollBar»)
| | |
| | | Button — ‘Line up’ (L-609, T311, R-588, B332)
| | | [‘Button’, ‘Line up’, ‘Line upButton’, ‘Button0’, ‘Button1’]
| | | child_window(title=»Line up», auto_id=»UpButton», control_type=»Button»)
| | |
| | | Button — ‘Line down’ (L-609, T954, R-588, B975)
| | | [‘Button2’, ‘Line downButton’, ‘Line down’]
| | | child_window(title=»Line down», auto_id=»DownButton», control_type=»Button»)
| |
| | ScrollBar — ‘Horizontal’ (L-1118, T975, R-609, B996)
| | [‘HorizontalScrollBar’, ‘ScrollBar2’, ‘Horizontal’]
| | child_window(title=»Horizontal», auto_id=»NonClientHorizontalScrollBar», control_type=»ScrollBar»)
| | |
| | | Button — ‘Column left’ (L-1118, T975, R-1097, B996)
| | | [‘Button3’, ‘Column left’, ‘Column leftButton’]
| | | child_window(title=»Column left», auto_id=»UpButton», control_type=»Button»)
| | |
| | | Button — ‘Column right’ (L-630, T975, R-609, B996)
| | | [‘Button4’, ‘Column rightButton’, ‘Column right’]
| | | child_window(title=»Column right», auto_id=»DownButton», control_type=»Button»)
| |
| | Thumb — » (L-609, T975, R-588, B996)
| | [‘Thumb’]
|
| StatusBar — ‘Status Bar’ (L-1118, T996, R-588, B1025)
| [‘StatusBar’, ‘Status Bar’, ‘Status BarStatusBar’]
| child_window(title=»Status Bar», auto_id=»1025″, control_type=»StatusBar»)
| |
| | Static — » (L0, T0, R0, B0)
| | [‘Static’, ‘Static0’, ‘Static1’]
| |
…
Подробнее можно изучить control identifiers блокнота в статье
«Автотестирование блокнота с pywinauto»
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(
title=‘Untitled — Notepad’, timeout=100
)
# app.UntitledNotepad.print_control_identifiers()
text_editor = app.UntitledNotepad.child_window(
title=«Text Editor»,
auto_id=«15»,
control_type=«Edit»
).wrapper_object()
text_editor.type_keys(
«Subscribe to t.me/aofeed», with_spaces=True
)
Часто контроллеров очень много и смотреть на их вывод в терминал неудобно.
Решается эта проблема записью в файл, который нужно указать как аргумент
app.MyApp.print_control_identifiers(filename=«controls.txt»)
Файл controls.txt будет создан в рабочей директории, то есть скорее всего рядом
с запускаемым .exe файлом а не рядом со скриптом.
Объекты во вложенных меню
Когда мы в первый раз использовали
print_control_identifiers()
был получен список всех контроллеров, доступных из стартового окна.
Чтобы полуить списки контроллеров, которые доступны из подменю, нужно сперва выполнить
click_input()
на нужное меню и затем сразу же выполнить
print_control_identifiers()
Действуя по этому алгоритму можно создать библиотеку из всех доступных контроллеров.
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(
title=‘Untitled — Notepad’, timeout=100
)
# app.UntitledNotepad.print_control_identifiers()
text_editor = app.UntitledNotepad.child_window(
title=«Text Editor»,
auto_id=«15»,
control_type=«Edit»
).wrapper_object()
text_editor.type_keys(
«Subscribe to t.me/aofeed», with_spaces=True
)
file_menu = app.UntitledNotepad.child_window(title=«File», control_type=«MenuItem»).wrapper_object()
file_menu.click_input()
app.UntitledNotepad.print_control_identifiers()
Control Identifiers:
Dialog — ‘*Untitled — Notepad’ (L-1512, T253, R641, B1009)
[‘*Untitled — Notepad’, ‘Dialog’, ‘*Untitled — NotepadDialog’]
child_window(title=»*Untitled — Notepad», control_type=»Window»)
|
| Menu — ‘File‘ (L-1503, T315, R-1234, B527)
| [‘File’, ‘FileMenu’, ‘Menu’, ‘Menu0’, ‘Menu1’, ‘File0’, ‘File1’]
| child_window(title=»File», control_type=»Menu»)
| |
| | MenuItem — ‘New Ctrl+N’ (L-1500, T318, R-1237, B342)
| | [‘New\tCtrl+N’, ‘New\tCtrl+NMenuItem’, ‘MenuItem’, ‘MenuItem0’, ‘MenuItem1’]
| | child_window(title=»New Ctrl+N», auto_id=»1″, control_type=»MenuItem»)
| |
| | MenuItem — ‘New Window Ctrl+Shift+N’ (L-1500, T342, R-1237, B366)
| | [‘MenuItem2’, ‘New Window\tCtrl+Shift+N’, ‘New Window\tCtrl+Shift+NMenuItem’]
| | child_window(title=»New Window Ctrl+Shift+N», auto_id=»8″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Open… Ctrl+O’ (L-1500, T366, R-1237, B390)
| | [‘Open…\tCtrl+O’, ‘Open…\tCtrl+OMenuItem’, ‘MenuItem3’]
| | child_window(title=»Open… Ctrl+O», auto_id=»2″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Save Ctrl+S’ (L-1500, T390, R-1237, B414)
| | [‘Save\tCtrl+SMenuItem’, ‘Save\tCtrl+S’, ‘MenuItem4’]
| | child_window(title=»Save Ctrl+S», auto_id=»3″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Save As… Ctrl+Shift+S’ (L-1500, T414, R-1237, B438)
| | [‘MenuItem5’, ‘Save As…\tCtrl+Shift+S’, ‘Save As…\tCtrl+Shift+SMenuItem’]
| | child_window(title=»Save As… Ctrl+Shift+S», auto_id=»4″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Page Setup…’ (L-1500, T445, R-1237, B469)
| | [‘Page Setup…MenuItem’, ‘MenuItem6’, ‘Page Setup…’]
| | child_window(title=»Page Setup…», auto_id=»5″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Print… Ctrl+P’ (L-1500, T469, R-1237, B493)
| | [‘Print…\tCtrl+P’, ‘MenuItem7’, ‘Print…\tCtrl+PMenuItem’]
| | child_window(title=»Print… Ctrl+P», auto_id=»6″, control_type=»MenuItem»)
| |
| | MenuItem — ‘Exit’ (L-1500, T500, R-1237, B524)
| | [‘Exit’, ‘ExitMenuItem’, ‘MenuItem8’]
| | child_window(title=»Exit», auto_id=»7″, control_type=»MenuItem»)
|
| Edit — ‘Text Editor’ (L-1503, T316, R632, B971)
| [‘Edit’, ‘Edit0’, ‘Edit1’]
| child_window(title=»Text Editor», auto_id=»15″, control_type=»Edit»)
| |
| | ScrollBar — ‘Vertical’ (L611, T316, R632, B950)
| | [‘Vertical’, ‘ScrollBar’, ‘VerticalScrollBar’, ‘ScrollBar0’, ‘ScrollBar1’]
| | child_window(title=»Vertical», auto_id=»NonClientVerticalScrollBar», control_type=»ScrollBar»)
| | |
| | | Button — ‘Line up’ (L611, T316, R632, B337)
| | | [‘Line up’, ‘Line upButton’, ‘Button’, ‘Button0’, ‘Button1’]
| | | child_window(title=»Line up», auto_id=»UpButton», control_type=»Button»)
| | |
| | | Button — ‘Line down’ (L611, T929, R632, B950)
| | | [‘Line down’, ‘Line downButton’, ‘Button2’]
| | | child_window(title=»Line down», auto_id=»DownButton», control_type=»Button»)
| |
…
Подробнее можно изучить control identifiers блокнота в статье
«Автотестирование блокнота с pywinauto»
Пример поиска вложенного объекта
Откроем новое окно.
Для этого в предыдущем выводе найдём
…
| | MenuItem — ‘New Window Ctrl+Shift+N’ (L-1500, T342, R-1237, B366)
| | [‘MenuItem2’, ‘New Window\tCtrl+Shift+N‘, ‘New Window\tCtrl+Shift+NMenuItem’]
| | child_window(title=»New Window Ctrl+Shift+N«, auto_id=»8″, control_type=»MenuItem»)
…
Скопируем строку с clild_window, но вместо
New Window Ctrl+Shift+N
используем
New Window\tCtrl+Shift+N
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(
title=‘Untitled — Notepad’, timeout=100
)
# app.UntitledNotepad.print_control_identifiers()
text_editor = app.UntitledNotepad.child_window(
title=«Text Editor»,
auto_id=«15»,
control_type=«Edit»
).wrapper_object()
text_editor.type_keys(
«Subscribe to t.me/aofeed», with_spaces=True
)
file_menu = app.UntitledNotepad.child_window(
title=«File»,
control_type=«MenuItem»
).wrapper_object()
file_menu.click_input()
app.UntitledNotepad.print_control_identifiers()
new_window = app.UntitledNotepad.child_window(
title=«New Window\tCtrl+Shift+N»,
auto_id=«8»,
control_type=«MenuItem»
).wrapper_object()
new_window.click_input()
Диалоговое окно
Для простоты не будем открывать новое окно.
Откроем блокнот, введём текст и с помощью контроллера close
…
| | Button — ‘Close’ (L573, T254, R633, B291)
| | [‘CloseButton’, ‘Close’, ‘Button7’]
| | child_window(title=»Close», control_type=»Button»)
…
закроем блокнот. Появится диалоговое окно Save, Don’t Save и так далее.
В этот момент нужно распечатать контроллеры этого диалогового окна и использовать нужный.
from pywinauto.application import Application
app = Application(backend=‘uia’).start(‘notepad.exe’)
app = Application(backend=‘uia’).connect(
title=‘Untitled — Notepad’, timeout=100
)
text_editor = app.UntitledNotepad.child_window(
title=«Text Editor»,
auto_id=«15»,
control_type=«Edit»
).wrapper_object()
text_editor.type_keys(
«Subscribe to t.me/aofeed», with_spaces=True
)
file_menu = app.UntitledNotepad.child_window(
title=«File», control_type=«MenuItem»
).wrapper_object()
file_menu.click_input()
close = app.UntitledNotepad.child_window(
title=«Close», control_type=«Button»
).wrapper_object()
close.click_input()
app.UntitledNotepad.print_control_identifiers()
dont_save = app.UntitledNotepad.child_window(
title=«Don’t Save», auto_id=«CommandButton_7», control_type=«Button»
).wrapper_object()
dont_save.click_input()
…
Control Identifiers:
Dialog — ‘*Untitled — Notepad’ (L321, T173, R769, B851)
[‘Dialog’, ‘*Untitled — Notepad’, ‘*Untitled — NotepadDialog’, ‘Dialog0’, ‘Dialog1’]
child_window(title=»*Untitled — Notepad», control_type=»Window»)
|
| Dialog — ‘Notepad’ (L1042, T568, R1500, B745)
| [‘Notepad’, ‘Dialog2’, ‘NotepadDialog’]
| child_window(title=»Notepad», control_type=»Window»)
| |
| | Static — ‘Do you want to save changes to Untitled?’ (L1064, T619, R1427, B647)
| | [‘Static’, ‘Do you want to save changes to Untitled?’, ‘Do you want to save changes to Untitled?Static’, ‘Static0’, ‘Static1’]
| | child_window(title=»Do you want to save changes to Untitled?», auto_id=»MainInstruction», control_type=»Text»)
| |
| | Static — » (L0, T0, R0, B0)
| | [‘Static2’]
| | child_window(auto_id=»ContentText», control_type=»Text»)
| |
| | Button — ‘Save’ (L1174, T696, R1261, B725)
| | [‘Button’, ‘Save’, ‘SaveButton’, ‘Button0’, ‘Button1’]
| | child_window(title=»Save», auto_id=»CommandButton_6″, control_type=»Button»)
| |
| | Button — ‘Don’t Save’ (L1269, T696, R1383, B725)
| | [‘Button2’, «Don’t SaveButton», «Don’t Save»]
| | child_window(title=»Don’t Save», auto_id=»CommandButton_7″, control_type=»Button»)
| |
| | Button — ‘Cancel’ (L1391, T696, R1478, B725)
| | [‘Cancel’, ‘Button3’, ‘CancelButton’]
| | child_window(title=»Cancel», auto_id=»CommandButton_2″, control_type=»Button»)
| |
| | TitleBar — » (L1051, T571, R1491, B606)
| | [‘TitleBar’, ‘TitleBar0’, ‘TitleBar1’]
| | |
| | | Button — ‘Close’ (L1448, T569, R1492, B606)
| | | [‘Button4’, ‘CloseButton’, ‘Close’, ‘CloseButton0’, ‘CloseButton1’, ‘Close0’, ‘Close1’]
| | | child_window(title=»Close», control_type=»Button»)
…
Контроллеры
Существует два набора контроллеров:
-
Для Win32
-
Для UIA
Поиск элемента для ввода текста
Допустим нужно ввести текст в такой элемент
www.testsetup.ru
Примерный порядок действий:
Запустить приложение.
Определить название (title) диалогового окна — в данном примере это App Name.
Распечатать контроллеры для диалогового окна
app = Application(backend=‘win32’).start(«path_to_exe»)
app = app.connect(best_match=«App Name», timeout=3)
dlg = app.AppName
dlg.print_ctrl_ids()
Где-то в выдаче будет похожий результат
Control Identifiers:
Dialog — ‘App Name’ (L735, T375, R1186, B777)
[‘App NameDialog’, ‘App Name’, ‘Dialog’]
child_window(title=»App Name», class_name=»#32770″)
|
…
| ComboBox — » (L819, T518, R1067, B539)
| [‘ComboBox’, ‘&Destination:ComboBox’, ‘ComboBox0’, ‘ComboBox1’]
| child_window(title=»», class_name=»ComboBox»)
| |
| | Edit — » (L822, T521, R1047, B536)
| | [‘&Destination:Edit’, ‘Edit’, ‘Edit0’, ‘Edit1’]
| | child_window(title=»», class_name=»Edit»)
…
Если бы в поле был текст, например abc, то он бы отобразился так:
…
| ComboBox — ‘abc‘ (L819, T518, R1067, B539)
| [‘ComboBox’, ‘&Destination:ComboBox’, ‘ComboBox0’, ‘ComboBox1’]
| child_window(title=»abc«, class_name=»ComboBox»)
| |
| | Edit — ‘abc‘ (L822, T521, R1047, B536)
| | [‘&Destination:Edit’, ‘Edit’, ‘Edit0’, ‘Edit1’]
| | child_window(title=»abc«, class_name=»Edit»)
…
Так как искомый элемент содержится внутри ComboBox можно перебрать все ComboBox с помощью found_index
Предположим, что нам повезло и нужный бокс имеет индекс 0.
destination = dlg.child_window(class_name=«ComboBox», found_index=0)
destination.print_ctrl_ids()
Control Identifiers:
ComboBox — ‘abc‘ (L819, T518, R1067, B539)
[‘ComboBox’]
child_window(title=»abc«, class_name=»ComboBox»)
|
| Edit — ‘abc‘ (L822, T521, R1047, B536)
| [‘Edit’]
| child_window(title=»abc«, class_name=»Edit»)
Аналогичным способом найдём вложенное поле для ввода. Так как дочерный элемент всего один здесь тоже будет индекс 0.
text_input = destination.child_window(class_name=«Edit», found_index=0)
text_input.set_text(«https://devhops.ru»)
Результат:
www.testsetup.ru
Полный код
app = Application(backend=‘win32’).start(«path_to_exe»)
app = app.connect(best_match=«App Name», timeout=3)
dlg = app.AppName
# dlg.print_ctrl_ids()
destination = dlg.child_window(class_name=«ComboBox», found_index=0)
# destination.print_ctrl_ids()
text_input = destination.child_window(class_name=«Edit», found_index=0)
text_input.set_text(«https://devhops.ru»)
app.windows()
Пример поиска по app.windows(). Подсмотрел
здесь
…
print([w.window_text() for w in app.windows()])
i = 0
for w in app.windows():
i += 1
print(i)
print(dir(w))
print(w.children())
if i == 1:
wind = w
print(dir(wind))
print(wind.children())
children = wind.children()
i = 0
for child in children:
i += 1
print(dir(child))
print(child.texts)
if i == 1:
child.click()
requirements.txt
certifi==2024.2.2
charset-normalizer==3.3.2
comtypes==1.4.1
idna==3.7
pywin32==306
pywinauto==0.6.8
requests==2.31.0
six==1.16.0
typing_extensions==4.12.2
urllib3==2.2.1
WMI==1.5.1
Common Files
from pywinauto import Desktop, Application
Application().start(‘explorer.exe «C:\Program Files»‘)
# connect to another process spawned by explorer.exe
# Note: make sure the script is running as Administrator!
app = Application(backend=«uia»).connect(path=«explorer.exe», title=«Program Files»)
app.ProgramFiles.set_focus()
common_files = app.ProgramFiles.ItemsView.get_item(‘Common Files’)
common_files.right_click_input()
app.ContextMenu.Properties.invoke()
# this dialog is open in another process (Desktop object doesn’t rely on any process id)
Properties = Desktop(backend=‘uia’).Common_Files_Properties
Properties.print_control_identifiers()
Properties.Cancel.click()
Properties.wait_not(‘visible’) # make sure the dialog is closed
Автор статьи: Андрей Олегович
Похожие статьи
Автоматизация | |
pywinauto | |
Ошибки | |
Python |