一旦你安装了pywinauto - 你怎么样? 第一个必要的事情是确定哪种可访问性技术(pywinauto的backend)可以用于您的应用程序。
Windows上受支持的辅助功能技术列表:
- Win32 API (
backend="win32"
) - 现在的默认backend
- MFC, VB6, VCL, 简单的WinForms控件和大多数旧的遗留应用程序
- MS UI Automation (
backend="uia"
)
- WinForms, WPF, Store apps, Qt5, 浏览器
注意: Chrome在启动之前需要
--force-renderer-accessibility
cmd标志。 由于comtypes Python库限制,不支持自定义属性和控件。
到目前为止,Linux上的AT SPI和Apple Accessibility API都是长期计划。
GUI对象检查/Spy工具
如果您仍然不确定哪个backend最适合您,请尝试使用免费提供的对象检查/Spy工具:从GitHub repo gui-inspect-tool下载它们.
- Spy++ 包含在MS Visual Studio发行版(甚至是Express或Community)中,可通过“开始”菜单访问。 它使用Win32 API。 这意味着如果Spy ++能够显示所有控件,那么“win32”`backend就是你需要的。 AutoIt Window Info工具是一种Spy ++克隆。
- Inspect.exe 是Microsoft创建的另一个很棒的工具。 它包含在Windows SDK中,因此可以在x64 Windows上的以下位置找到它:
C:\Program Files (x86)\Windows Kits\<winver>\bin\x64
将Inspect.exe切换到UIA mode(使用MS UI Automation)。 如果它可以显示比Spy ++更多的控件及其属性,那么可能是
"uia"
backend是你的选择。
- py_inspect 是基于pywinauto的多后端间谍工具的原型。 在可用后端之间切换可以通过“win32”和“uia”后端向您显示层次结构的差异。 py \ _inspect是SWAPY的未来替代品,仅在pywinauto == 0.5.4出现时支持“win32”后端。 由于现代pywinauto 0.6.0+架构,py \ _insins的初始实现仅包含大约150行代码。
如果所有检测工具都看不到某些或所有控件,则仍然可以通过使用基本模块鼠标和键盘生成鼠标和键盘事件来控制应用程序。
自动化的切入点
所以你有一个应用程序,你知道它支持上面提到的一种可访问性技术。 下一步是什么?
首先,您应该启动应用程序或连接到现有的应用程序实例。 它可以使用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')
如果你想要跨越流程边界(比如Win10计算器令人惊讶地在多个流程中绘制它的小部件),你的入口点是一个Desktop
对象。
from subprocess import Popen
from pywinauto import Desktop
Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')
Application和Desktop对象都是backend特定的。 无需在后续操作中明确使用backend名称。
窗口规格
这是高级pywinauto API的核心概念。 您可以近似或更详细地描述任何窗口或控件,即使它尚不存在或已经关闭。 窗口规范还保留有关将用于获得真实窗口或控件的匹配/搜索算法的信息。
让我们创建一个详细的窗口规范:
>>> dlg_spec = app.window(title=u'无标题 - 记事本')
>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>
>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>
实际窗口查找由wrapper_object()
方法执行。 它返回实际现有窗口/控件的一些包装器或引发ElementNotFoundError
。 这个包装器可以通过发送动作或检索数据来处理窗口/控件。
但是Python可以隐藏这个wrapper_object()
调用,这样你就可以在生产中拥有更紧凑的代码。 以下陈述完全相同:
dlg_spec.wrapper_object().minimize() # 在调试时
dlg_spec.minimize() # 在生产环境中
创建窗口规范有许多可能的标准。 这只是几个例子。
# 可以是多层次的
app.window(title_re='.* - Notepad$').window(class_name='Edit')
# 可以结合标准
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')
可以在pywinauto.findwindows.find_elements()函数中找到可能的标准列表。
属性解析魔法
Python通过动态解析对象属性简化了创建窗口规范。 但是一个属性名称与任何变量名称具有相同的限制:没有空格,逗号和其他特殊符号。 但幸运的是pywinauto使用“最佳匹配”算法来查找拼写错误和小变化。
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()
方法。 可能的“best_match”名称显示为树中每个控件的Python列表。 也可以从方法输出中复制更详细的窗口规范。 比方说 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")
看看这些例子
包括以下示例: 注意: 示例取决于语言 - 它们仅适用于为其编程的产品语言。 除非突出显示,否则所有示例都已编程为英语软件。
-
mspaint.py
控制画图 -
notepad_fast.py
使用快速计时设置来控制记事本 -
notepad_slow.py
使用慢速计时设置来控制记事本 -
notepad_item.py
使用item而不是属性访问来控制记事本。 -
misc_examples.py
显示一些例外以及如何获取控件标识符。 -
save_from_internet_explorer.py
从Internet Explorer保存网页。 -
save_from_firefox.py
从Firefox保存网页。 -
get_winrar_info.py
如何进行多语言自动化的示例。 这不是一个理想的例子(适用于法语,捷克语和德语WinRar) -
forte_agent_sample.py
处理复杂应用程序的示例,该应用程序非常动态,并且在启动时经常提供不同的对话框。 -
windowmediaplayer.py
只是另一个例子 - 处理ListView中的复选框。 -
test_sakura.py
,test_sakura2.py
两个自动化日本产品的例子。
在命令行自动记事本
请在下面找到一个示例运行
C:\>python
Python 2.4.2 (#67, Sep 28 2005, 12:41:11) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(1) >>> from pywinauto import application
(2) >>> app = application.Application()
(3) >>> app.start("Notepad.exe")
<pywinauto.application.Application object at 0x00AE0990>
(4) >>> app.UntitledNotepad.draw_outline()
(5) >>> app.UntitledNotepad.menu_select("Edit -> Replace")
(6) >>> app.Replace.print_control_identifiers()
Control Identifiers:
Dialog - 'Replace' (L179, T174, R657, B409)
['ReplaceDialog', 'Dialog', 'Replace']
child_window(title="Replace", class_name="#32770")
|
| Static - 'Fi&nd what:' (L196, T230, R292, B246)
| ['Fi&nd what:Static', 'Fi&nd what:', 'Static', 'Static0', 'Static1']
| child_window(title="Fi&nd what:", class_name="Static")
|
| Edit - '' (L296, T226, R524, B250)
| ['Fi&nd what:Edit', 'Edit', 'Edit0', 'Edit1']
| child_window(class_name="Edit")
|
| Static - 'Re&place with:' (L196, T264, R292, B280)
| ['Re&place with:', 'Re&place with:Static', 'Static2']
| child_window(title="Re&place with:", class_name="Static")
|
| Edit - '' (L296, T260, R524, B284)
| ['Edit2', 'Re&place with:Edit']
| child_window(class_name="Edit")
|
| CheckBox - 'Match &whole word only' (L198, T304, R406, B328)
| ['CheckBox', 'Match &whole word onlyCheckBox', 'Match &whole word only', 'CheckBox0', 'CheckBox1']
| child_window(title="Match &whole word only", class_name="Button")
|
| CheckBox - 'Match &case' (L198, T336, R316, B360)
| ['CheckBox2', 'Match &case', 'Match &caseCheckBox']
| child_window(title="Match &case", class_name="Button")
|
| Button - '&Find Next' (L536, T220, R636, B248)
| ['&Find Next', '&Find NextButton', 'Button', 'Button0', 'Button1']
| child_window(title="&Find Next", class_name="Button")
|
| Button - '&Replace' (L536, T254, R636, B282)
| ['&ReplaceButton', '&Replace', 'Button2']
| child_window(title="&Replace", class_name="Button")
|
| Button - 'Replace &All' (L536, T288, R636, B316)
| ['Replace &AllButton', 'Replace &All', 'Button3']
| child_window(title="Replace &All", class_name="Button")
|
| Button - 'Cancel' (L536, T322, R636, B350)
| ['CancelButton', 'Cancel', 'Button4']
| child_window(title="Cancel", class_name="Button")
|
| Button - '&Help' (L536, T362, R636, B390)
| ['&Help', '&HelpButton', 'Button5']
| child_window(title="&Help", class_name="Button")
|
| Static - '' (L196, T364, R198, B366)
| ['ReplaceStatic', 'Static3']
| child_window(class_name="Static")
(7) >>> app.Replace.Cancel.click()
(8) >>> app.UntitledNotepad.Edit.type_keys("Hi from Python interactive prompt %s" % str(dir()), with_spaces = True)
<pywinauto.controls.win32_controls.EditWrapper object at 0x00DDC2D0>
(9) >>> app.UntitledNotepad.menu_select("File -> Exit")
(10) >>> app.Notepad.DontSave.click()
>>>
- 导入pywinauto.application模块(通常是您需要直接导入的唯一模块)
- 创建一个Application实例。 对该应用程序的所有访问都是通过该对象完成的。
- 我们在步骤2中创建了一个Application实例,但是我们没有提供它所引用的Windows应用程序的任何信息。 通过使用start()方法,我们执行该应用程序并将其连接到Application实例应用程序。
- 在记事本对话框周围绘制一个绿色矩形 - 这样我们就知道我们有正确的窗口。
- 从应用程序所连接的应用程序的“记事本”对话框的“编辑”菜单中选择“替换”项。 此操作将显示“替换”对话框。
- 在“替换”对话框上打印控件的标识符,例如,“替换”对话框上的第一个编辑控件可以通过以下任何标识符引用:
app.Replace.Edit
app.Replace.Edit0
app.Replace.Edit1
app.FindwhatEdit
最后一个是让用户在阅读脚本后最好了解脚本的功能。
- 关闭“替换”对话框。 (在脚本文件中,使用close_click()而不是click()更安全,因为close_click()会等待一段时间以给窗口时间关闭对话框。)
- 让我们在记事本文本区域中键入一些文本。 没有
with_spaces
参数,就不会输入空格。 请参阅SendKeys的文档,了解此方法,因为它是SendKeys的一个薄包装器。 - 要求退出记事本
- 我们将被询问是否要保存 - 单击“否”按钮。