pywinauto 使用指南

简介

pywinauto 是一个用于 Windows 系统下的 GUI 自动化测试和操作的 Python 库。它允许您通过代码自动化控制 Windows 应用程序,执行点击、输入文本、获取界面元素等操作,非常适合 GUI 自动化测试和重复性操作的自动化。

当前版本: 0.6.9

主要功能

  • 自动化启动和连接 Windows 应用程序
  • 查找和操作窗口及控件
  • 模拟用户操作(点击、输入、拖拽等)
  • 获取窗口和控件的属性和状态
  • 支持多种后端(backend)模式,包括 UIA(UI Automation)和传统的 win32

环境准备

系统要求

  • Windows 操作系统(Windows 7/8/10/11)
  • Python 3.6 或更高版本
  • 仅支持 Windows 平台,不支持 Linux 或 macOS

安装依赖

# 安装主要库
pip install pywinauto

# 安装其他可能用到的依赖
pip install pillow  # 用于图像处理,截图功能需要
pip install opencv-python  # 用于图像识别(可选)
pip install python-cv  # 用于复杂图像处理(可选)

后端选择

pywinauto 支持两种后端:

  1. UIA (UI Automation) - 推荐用于 Windows 7 及以上系统,支持更多的控件类型

    app = Application(backend="uia")
  2. Win32 - 用于较老的应用程序或特殊情况

    app = Application(backend="win32")

开发工具推荐

为了更方便地识别和定位 Windows 应用程序中的控件,推荐使用以下工具:

  • Inspect.exe - Windows SDK 自带的 UI 检查工具
  • Spy++ - Visual Studio 自带的窗口检查工具
  • UISpy - Windows SDK 工具
  • Accessibility Insights for Windows - 微软开发的辅助工具

安装方法

pip install pywinauto

基本使用

导入必要模块

from pywinauto.application import Application
from pywinauto.findwindows import find_elements, find_element
import time

启动应用程序

# 使用 UIA 后端启动应用程序
app = Application(backend="uia").start("应用程序路径")

# 示例:启动记事本
app = Application(backend="uia").start("notepad.exe")

连接到已运行的应用程序

# 通过窗口标题连接
app = Application(backend="uia").connect(title="窗口标题")

# 通过窗口标题的正则表达式连接
app = Application(backend="uia").connect(title_re="窗口标题.*")

# 通过进程 ID 连接
app = Application(backend="uia").connect(process=1234)

窗口和控件操作

# 获取顶层窗口
main_window = app.top_window()

# 等待窗口准备就绪
main_window.wait('ready', timeout=10)

# 打印窗口内所有控件的层次结构
print(main_window.dump_tree())

# 通过标题和控件类型查找特定控件
button = main_window.child_window(title="确定", control_type="Button")

# 点击按钮
button.click()

# 向文本框输入内容
edit = main_window.child_window(control_type="Edit")
edit.type_keys("Hello World!")

# 绘制红色边框以突出显示控件(调试时很有用)
button.draw_outline(colour='red', thickness=2)

Desktop 对象

可以使用 Desktop 对象直接访问桌面上的窗口:

from pywinauto import Desktop

# 获取桌面窗口
desktop = Desktop(backend="uia")

# 获取所有可见窗口
windows = desktop.windows()

# 通过标题访问特定窗口
notepad_window = desktop['记事本']

进阶功能详解

1. 保存控件菜单树结构 print_control_identifiers()

此方法可以打印出窗口中所有控件的标识符和属性,是调试和开发自动化脚本的重要工具:

# 打印窗口所有控件的标识符信息
main_window.print_control_identifiers()

# 也可以针对特定控件打印其子控件的标识符
panel = main_window.child_window(title="面板", control_type="Pane")
panel.print_control_identifiers()

输出示例:

Control Identifiers:
Button - '确定'    (L1373, T636, R1455, B666)
['确定Button', '确定', 'Button', 'Button0', 'Button1']
Button - '取消'    (L1373, T589, R1455, B619)
['取消Button', '取消', 'Button2']

2. 设置全局等待时间 Timings

pywinauto 查找窗口和控件时会有超时时间与轮询机制,可以通过 timings 模块设置全局等待时间:

from pywinauto import timings

# 设置默认超时时间(秒)
timings.Timings.window_find_timeout = 5.0  # 查找窗口超时
timings.Timings.app_start_timeout = 20.0   # 启动应用程序超时
timings.Timings.app_connect_timeout = 5.0  # 连接应用程序超时
timings.Timings.after_click_wait = 0.5     # 点击后等待时间

# 恢复默认时间设置
timings.Timings.defaults()

timings 模块提供了三种预设模式:

# 快速模式 - 减少各种操作的等待时间
timings.Timings.fast()

# 默认模式 - 使用默认等待时间设置
timings.Timings.defaults()

# 慢速模式 - 增加各种操作的等待时间,适用于较慢的应用程序
timings.Timings.slow()

以下是可以调整的各个时序设置:

window_find_timeout (default 5)        # 查找窗口的超时时间
window_find_retry (default .09)        # 查找窗口的重试间隔
app_start_timeout (default 10)         # 启动应用程序的超时时间
app_start_retry (default .90)          # 启动应用程序的重试间隔
app_connect_timeout (default 5.)       # 连接应用程序的超时时间
app_connect_retry (default .1)         # 连接应用程序的重试间隔
cpu_usage_interval (default .5)        # CPU使用率检查间隔
cpu_usage_wait_timeout (default 20)    # 等待CPU使用率降低的超时时间
exists_timeout (default .5)            # 检查元素存在的超时时间
exists_retry (default .3)              # 检查元素存在的重试间隔
after_click_wait (default .09)         # 点击后等待时间
after_clickinput_wait (default .09)    # 使用点击输入后等待时间
after_menu_wait (default .1)           # 菜单操作后等待时间
after_sendkeys_key_wait (default .01)  # 发送按键后每个键的等待时间
after_button_click_wait (default 0)    # 按钮点击后等待时间
before_closeclick_wait (default .1)    # 关闭点击前等待时间
closeclick_retry (default .05)         # 关闭点击重试间隔
closeclick_dialog_close_wait (default 2) # 关闭对话框后等待时间
after_closeclick_wait (default .2)     # 关闭点击后等待时间
after_windowclose_timeout (default 2)  # 窗口关闭后超时时间
after_windowclose_retry (default .5)   # 窗口关闭后重试间隔
after_setfocus_wait (default .06)      # 设置焦点后等待时间
setfocus_timeout (default 2)           # 设置焦点的超时时间
setfocus_retry (default .1)            # 设置焦点的重试间隔
after_setcursorpos_wait (default .01)  # 设置光标位置后等待时间
sendmessagetimeout_timeout (default .01) # 发送消息超时时间
after_tabselect_wait (default .05)     # 选项卡选择后等待时间
after_listviewselect_wait (default .01) # 列表视图选择后等待时间
after_listviewcheck_wait (default .001) # 列表视图勾选后等待时间
listviewitemcontrol_timeout (default 1.5) # 列表视图项控制的超时时间
after_treeviewselect_wait (default .1) # 树视图选择后等待时间
after_toobarpressbutton_wait (default .01) # 工具栏按钮按下后等待时间
after_updownchange_wait (default .1)   # 上下更改后等待时间
after_movewindow_wait (default 0)      # 移动窗口后等待时间
after_buttoncheck_wait (default 0)     # 按钮勾选后等待时间
after_comboboxselect_wait (default .001) # 组合框选择后等待时间
after_listboxselect_wait (default 0)   # 列表框选择后等待时间
after_listboxfocuschange_wait (default 0) # 列表框焦点更改后等待时间
after_editsetedittext_wait (default 0) # 编辑框设置文本后等待时间
after_editselect_wait (default .02)    # 编辑框选择后等待时间
drag_n_drop_move_mouse_wait (default .1) # 拖放移动鼠标等待时间
before_drag_wait (default .2)          # 拖动前等待时间
before_drop_wait (default .1)          # 放下前等待时间
after_drag_n_drop_wait (default .1)    # 拖放后等待时间
scroll_step_wait (default .1)          # 滚动步骤等待时间

适当调整这些时间设置可以提高自动化脚本的稳定性和成功率。对于较慢的应用程序,增加等待时间;对于响应迅速的应用程序,可以减少等待时间以提高效率。

3. 鼠标操作与滚动列表

鼠标操作包括点击、右键、双击、拖拽等:

# 直接使用鼠标模块进行操作
from pywinauto import mouse

# 单击鼠标左键(指定坐标位置)
mouse.click(coords=(100, 100))

# 鼠标右击
mouse.right_click(coords=(100, 200))

# 鼠标双击
mouse.double_click(coords=(100, 200))

# 鼠标长按、拖动、释放(拖拽操作)
mouse.press(coords=(200, 400))
mouse.move(coords=(100, 0))  # 相对移动
mouse.release(coords=(300, 400))

# 鼠标滚轮操作
mouse.scroll(coords=(0, 0), wheel_dist=1)  # 正数向上滚动,负数向下滚动

# 鼠标中键单击
mouse.wheel_click(coords=(0, 0))

# 控件级别的鼠标操作
button.click()                 # 左键点击控件
button.click_input()           # 模拟真实鼠标点击(移动鼠标到位置再点击)
button.right_click()           # 右键点击控件
button.right_click_input()     # 模拟真实鼠标右键点击
button.double_click()          # 双击控件
button.double_click_input()    # 模拟真实鼠标双击

滚动列表控件

滚动列表的方法有两种:

# 方法1:使用控件的滚动方法(某些控件支持)
list_control = main_window.child_window(control_type="List")
list_control.scroll("down", "page", count=2)  # 向下滚动2页
list_control.scroll("up", "line", count=5)    # 向上滚动5行

# 方法2:使用鼠标滚轮滚动(更通用)
# 首先获取控件的矩形区域
list_rect = list_control.rectangle()
# 然后在该区域内使用鼠标滚轮
mouse.scroll(coords=(list_rect.left + 20, list_rect.top + 30), wheel_dist=-2)  # 向下滚动,负数表示向下

# 实际应用示例:滚动微信聊天记录
app = Application(backend='uia').connect(path=r'C:\Program Files (x86)\Tencent\WeChat\WeChat.exe')
win = app.window(title="微信", class_name='WeChatMainWndForPC')
chat_list = win.child_window(title="会话", control_type="List").rectangle()
mouse.scroll(coords=(chat_list.left + 20, chat_list.top + 30), wheel_dist=-2)  # 向下滚动

注意:wheel_dist参数的值为正表示向上滚动,为负表示向下滚动,数值大小决定滚动的距离。

4. 查找多个相同控件使用 found_index

当同一个窗口中有多个相同属性的控件时,直接使用child_window()可能会引发ElementAmbiguousError错误。这时可以使用found_index参数来指定第几个匹配的控件:

# 获取第一个匹配的按钮
first_button = main_window.child_window(title="下一步", control_type="Button", found_index=0)

# 获取第二个匹配的按钮
second_button = main_window.child_window(title="下一步", control_type="Button", found_index=1)

# 获取所有匹配的按钮
all_buttons = main_window.children(title="下一步", control_type="Button")

实际应用示例 - 处理记事本中的多个菜单栏:

from pywinauto import Application

# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")

# 输入内容
win.child_window(title="文本编辑器").set_text("hello world")

# 查找MenuBar - 如果直接查找会报错
# menu = win.child_window(control_type="MenuBar")  # 这会引发错误

# 查找第一个MenuBar
menu0 = win.child_window(control_type="MenuBar", found_index=0)
print(menu0.window_text())

# 查找第二个MenuBar
menu1 = win.child_window(control_type="MenuBar", found_index=1)
print(menu1.window_text())

错误示例:当有多个匹配元素但没有使用found_index时,会引发以下错误:

pywinauto.findwindows.ElementAmbiguousError: There are 2 elements that match the criteria
{'control_type': 'MenuBar', 'top_level_only': False, 'parent': <uia_element_info.UIAElementInfo - '无标题 - 记事本', Notepad, 460858>, 'backend': 'uia'}

注意found_index参数的索引从 0 开始,即 0 表示第一个匹配的控件,1 表示第二个,以此类推。

5. 等待方法 wait() 和 wait_not()

详细的等待方法使用:

# 等待窗口处于特定状态
window.wait('ready')                    # 等待窗口准备就绪
window.wait('exists')                   # 等待窗口存在
window.wait('visible')                  # 等待窗口可见
window.wait('enabled')                  # 等待窗口启用

# 等待窗口不处于特定状态
window.wait_not('visible')              # 等待窗口不可见
window.wait_not('exists')               # 等待窗口不存在

# 使用超时参数
window.wait('ready', timeout=30)        # 最多等待30秒
window.wait_not('exists', timeout=10)   # 最多等待10秒

6. 组合框控件 ComboBox 操作

组合框控件(ComboBox)是 Windows 应用程序中常见的下拉选择控件。pywinauto 提供了两种操作组合框的方法:

# 方法1: 使用select方法直接选择选项
# 获取ComboBox控件
combo_box = main_window.child_window(control_type="ComboBox")

# 展开下拉列表
combo_box.expand()

# 选择特定选项(直接选择,但屏幕可能会闪烁)
combo_box.select("选项文本")

# 获取所有选项
options = combo_box.item_texts()

# 通过索引选择
combo_box.select(2)  # 选择第三个选项(索引从0开始)

# 方法2: 更接近真实操作的方法(推荐)
# 1.先点开选项
combo_box.click_input()

# 2.选择内容
main_window.child_window(title="选项文本", control_type="ListItem").click_input()

实际应用示例 - 操作记事本的编码选择:

from pywinauto import Application

# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")

# 输入内容
win.child_window(title="文本编辑器").set_text("hello world")

# 文件-另存为
win.menu_select('文件(F) -> 另存为(A)...')

# 操作弹出文件选择框
save_win = win.child_window(title="另存为", control_type="Window")

# 方法1: 使用select方法(可能导致屏幕闪烁)
save_win.child_window(title="编码(E):", control_type="ComboBox").select("UTF-8")

# 方法2: 更接近真实操作的方法(推荐)
# 1.先点开选项
save_win.child_window(title="编码(E):", control_type="ComboBox").click_input()
# 2.选择内容
save_win.child_window(title="UTF-8", control_type="ListItem").click_input()

注意select()方法虽然简便,但可能会导致屏幕闪烁;而使用click_input()方法分两步操作更接近真实手工操作,通常是更稳定的选择。

7. 窗口和控件截图 capture_as_image()

pywinauto 提供了对窗口和控件进行截图的功能,这在调试和报告生成中非常有用:

# 安装必要的依赖
# pip install Pillow  # PIL库是必需的,用于图像处理

from pywinauto import Application

# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")

# 对整个窗口截图
window_image = win.capture_as_image()
window_image.save("window_screenshot.png")

# 对特定控件截图
menu = win.child_window(title="应用程序", auto_id="MenuBar", control_type="MenuBar")
menu_image = menu.capture_as_image()
menu_image.save("menu_screenshot.png")

# 对按钮控件截图
button_image = button.capture_as_image()
button_image.save("button_screenshot.png")

# 截取特定区域(需要使用PIL库的ImageGrab)
from PIL import ImageGrab
region_image = ImageGrab.grab(bbox=(100, 100, 300, 300))  # (left, top, right, bottom)
region_image.save("region_screenshot.png")

注意事项

  1. 使用capture_as_image()功能需要安装 PIL 库 (pip install Pillow)
  2. 窗口截图可能会包含其他窗口的背景,特别是当窗口有透明部分时
  3. 控件截图更加精确,只截取控件本身的区域
  4. 可以使用 PIL 库的其他功能对截图进行处理,如裁剪、缩放、添加文字等

8. 键盘快捷键操作 send_keys()

pywinauto 提供了强大的键盘模拟功能,可以发送各种键盘按键和组合键:

from pywinauto.keyboard import send_keys

# 基本按键
send_keys("Hello World")          # 输入文本
send_keys("{ENTER}")             # 回车键
send_keys("{TAB}")               # Tab键
send_keys("{F1}")                # F1键

# 组合键
send_keys("^a")                  # Ctrl+A(全选)
send_keys("^c")                  # Ctrl+C(复制)
send_keys("^v")                  # Ctrl+V(粘贴)
send_keys("+{END}")              # Shift+End(选择到行尾)
send_keys("%{F4}")               # Alt+F4(关闭窗口)

# 特殊按键
send_keys("{VK_SPACE}")          # 空格键
send_keys("{BACKSPACE}")         # 退格键
send_keys("{DELETE}")            # 删除键
send_keys("{PAUSE}")             # 暂停键

# 直接向控件发送按键
edit_box.type_keys("文本内容{ENTER}")

控件文本输入的两种方法对比

  1. set_text方法:

    • 直接设置控件的文本值
    • 光标位置在最左边
    • 按 enter 后,文本被换行
    • 不支持快捷键操作
    • 示例:edit_box.set_text("hello world")
  2. type_keys方法:

    • 模拟真实的键盘输入
    • 支持中文输入
    • 光标位置在最右边
    • 支持各种快捷键操作
    • 示例:edit_box.type_keys("hello world^s") # 输入文本并保存(Ctrl+S)

示例 - 在记事本中使用快捷键:

from pywinauto import Application
from pywinauto.keyboard import send_keys
import time

# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")

# 使用type_keys输入文本
win.type_keys("Hello World!^a")  # 输入文本并全选(Ctrl+A)
time.sleep(1)

# 复制文本
send_keys("^c")
time.sleep(1)

# 粘贴文本
send_keys("^v^v")  # 粘贴两次
time.sleep(1)

# 使用Alt+F4关闭记事本
send_keys("%{F4}")

推荐:对于文本输入,通常建议使用type_keys()方法,因为它更接近真实用户操作,支持更多功能,并且在大多数情况下更可靠。

9. 操作 MenuItem 菜单项

pywinauto 提供了多种操作应用程序菜单的方法:

# 方法1: 使用menu_select方法(最简便的方法)
# 通过路径操作菜单项
main_window.menu_select("文件->打开")  # 点击"文件"菜单下的"打开"子菜单

# 对于包含快捷键标记的菜单项
main_window.menu_select("文件(F)->打开(O)")  # 带有快捷键标记的菜单项
main_window.menu_select('文件(&F)->打开(&O)')  # 另一种快捷键标记形式

# 方法2: 获取菜单对象,然后操作
# 获取菜单对象
menu = main_window.menu()

# 获取主菜单项并点击
file_menu = menu.item_by_path("文件")
file_menu.click()

# 获取子菜单项并点击
open_menu = menu.item_by_path("文件->打开")
open_menu.click()

# 方法3: 使用快捷键访问菜单
from pywinauto.keyboard import send_keys
send_keys("%f")  # Alt+F 打开"文件"菜单
send_keys("o")   # 选择"打开"子菜单

实际应用示例 - 操作记事本的菜单:

from pywinauto import Application
import time

# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
win.wait('ready')

# 方法1: 使用menu_select操作"文件"菜单中的"页面设置"
win.menu_select("文件(F)->页面设置(U)...")
time.sleep(1)

# 关闭页面设置对话框
page_setup = app.window(title="页面设置")
page_setup.child_window(title="确定", control_type="Button").click()

# 方法2: 使用menu对象操作"编辑"菜单
menu = win.menu()
edit_menu = menu.item_by_path("编辑(E)")
edit_menu.click()
time.sleep(0.5)

# 点击"编辑"菜单下的"时间/日期"
date_menu = menu.item_by_path("编辑(E)->时间/日期(D)")
date_menu.click()

# 方法3: 使用快捷键
send_keys("^a")  # Ctrl+A 全选
send_keys("^x")  # Ctrl+X 剪切

注意事项

  1. 在使用menu_select()时,菜单路径使用"->"分隔
  2. 如果菜单项有特殊字符,如"查找和替换",也可以直接包含在路径中
  3. 对于一些应用程序,菜单项可能包含访问键标记(如"文件(&F)"),需要正确包含这些标记
  4. 如果菜单项没有文本或文本识别有问题,可以使用索引方式,如"#0->#1"表示第一个主菜单的第二个子菜单

10. 获取控件属性和文本内容

# 获取控件文本
text = edit_box.texts()                # 返回文本列表
text_str = edit_box.window_text()      # 返回文本字符串

# 获取控件属性
enabled = button.is_enabled()          # 控件是否启用
visible = button.is_visible()          # 控件是否可见
rect = button.rectangle()              # 控件矩形区域

# 获取特定属性
control_type = button.element_info.control_type
automation_id = button.element_info.automation_id
class_name = button.element_info.class_name

# 获取复选框或单选按钮状态
checked = checkbox.is_checked()        # 复选框是否勾选

11. 操作弹出文件选择框

# 点击"打开文件"按钮
open_button.click()

# 等待文件选择对话框出现
file_dialog = app.window(title="打开")
file_dialog.wait('ready', timeout=10)

# 输入文件路径
file_path_edit = file_dialog.child_window(control_type="Edit")
file_path_edit.set_text("C:\\path\\to\\file.txt")

# 或者直接输入文件名
file_name_edit = file_dialog.child_window(control_type="Edit", found_index=0)
file_name_edit.set_text("file.txt")

# 点击"打开"按钮确认
open_dialog_button = file_dialog.child_window(title="打开", control_type="Button")
open_dialog_button.click()

12. 操作弹出新窗口上按钮

# 点击会弹出新窗口的按钮
button_popup = main_window.child_window(title="设置", control_type="Button")
button_popup.click()

# 等待新窗口出现
popup_window = app.window(title="设置对话框")
popup_window.wait('ready', timeout=10)

# 操作新窗口上的控件
ok_button = popup_window.child_window(title="确定", control_type="Button")
ok_button.click()

# 等待新窗口关闭
popup_window.wait_not('exists', timeout=5)

13. 操作级联菜单 menu_select()

# 复杂的多级菜单操作
main_window.menu_select("文件->导出->PDF文件")

# 如果菜单项有特殊字符或空格,使用引号
main_window.menu_select("编辑->查找和替换->高级替换")

# 使用索引操作菜单(如菜单项没有文本)
main_window.menu_select("#0->#1->#0")  # 第一个主菜单->第二个子菜单->第一个子子菜单

14. 操作窗口控件 child_window()

更多控件查找和操作方法:

# 使用多种属性组合查找控件
button = main_window.child_window(title="保存", control_type="Button", auto_id="saveButton")

# 使用部分标题匹配
button = main_window.child_window(title_re="保存.*", control_type="Button")

# 获取所有特定类型的控件
all_buttons = main_window.children(control_type="Button")
all_edit_boxes = main_window.children(control_type="Edit")

# 获取直接子控件vs所有后代控件
direct_children = main_window.children()
all_descendants = main_window.descendants()

# 使用相对位置查找
above_control = main_window.child_window(control_type="Button", top_level_only=True)
below_control = above_control.get_next()
previous_control = below_control.get_previous()

15. 操作指定 window 窗口

# 通过标题获取窗口对象
notepad_window = app.window(title="无标题 - 记事本")

# 通过类名获取窗口
dialog = app.window(class_name="#32770")

# 通过自动化ID获取窗口
main_window = app.window(auto_id="mainWindow")

# 窗口操作
notepad_window.minimize()        # 最小化窗口
notepad_window.maximize()        # 最大化窗口
notepad_window.restore()         # 还原窗口
notepad_window.close()           # 关闭窗口

# 窗口状态检查
is_minimized = notepad_window.is_minimized()
is_maximized = notepad_window.is_maximized()
is_normal = notepad_window.is_normal()

16. 启动指定应用程序

# 简单启动(使用系统默认路径)
app = Application(backend="uia").start("notepad.exe")

# 指定完整路径启动
app = Application(backend="uia").start(r"C:\Windows\System32\notepad.exe")

# 启动并带参数
app = Application(backend="uia").start(r"notepad.exe C:\temp\test.txt")

# 启动并等待初始化完成
app = Application(backend="uia").start("explorer.exe")
app.wait_cpu_usage_lower(threshold=5, timeout=30)  # 等待CPU使用率低于5%

# 以管理员权限启动
app = Application(backend="uia").start("cmd.exe", wait_for_idle=False)

错误处理与调试

1. 异常处理

使用 try-except 捕获常见异常:

from pywinauto.findwindows import ElementNotFoundError, ElementAmbiguousError

try:
    button = main_window.child_window(title="确定", control_type="Button")
    button.click()
except ElementNotFoundError:
    print("未找到指定按钮")
    # 进行替代操作
except ElementAmbiguousError:
    print("找到多个匹配的按钮,请使用更精确的条件")
    # 尝试使用更多条件进行查找

2. 调试技巧

调试自动化脚本时的有用方法:

# 1. 打印应用程序中所有顶层窗口
print(app.windows())

# 2. 打印当前窗口所有控件
main_window.print_control_identifiers(depth=None)  # 打印所有层级
main_window.print_control_identifiers(depth=3)     # 只打印3层深度

# 3. 将控件结构保存到文件
with open('controls.txt', 'w', encoding='utf-8') as f:
    f.write(main_window.print_control_identifiers(depth=None, filename=None))

# 4. 高亮显示找到的控件
button.draw_outline(colour='red', thickness=2)

# 5. 在弹出对话框前暂停脚本执行
input("按Enter键继续...")

3. 性能优化

提高自动化脚本性能的方法:

# 1. 减少不必要的查找操作
button = main_window.child_window(title="确定", control_type="Button")
button.click()
button.wait_not('visible')  # 使用同一个button对象

# 2. 缓存窗口对象
notepad_window = app.window(title="无标题 - 记事本")
# 在多次操作同一窗口时重用此对象

# 3. 使用更精确的查找条件
button = main_window.child_window(title="确定", control_type="Button", auto_id="OKButton")
# 添加auto_id等属性可以加快查找速度

# 4. 设置适当的超时时间
from pywinauto import timings
timings.Timings.window_find_timeout = 2.0  # 默认为5秒,可根据需要调整

实际应用场景

1. 文本编辑器自动化

# 自动化操作记事本
app = Application(backend="uia").start("notepad.exe")
notepad = app.window(title_re=".*记事本")
notepad.wait('ready')

# 输入文本
notepad.type_keys("这是一段自动输入的文本", with_spaces=True)

# 保存文件
notepad.menu_select("文件->保存")
save_dialog = app.window(title="另存为")
save_dialog.wait('ready')
file_name_edit = save_dialog.child_window(control_type="Edit")
file_name_edit.set_text("test.txt")
save_button = save_dialog.child_window(title="保存", control_type="Button")
save_button.click()

2. 表单填写自动化

# 连接到已打开的应用程序
app = Application(backend="uia").connect(title_re="注册表单.*")
form_window = app.window(title_re="注册表单.*")

# 填写表单字段
name_edit = form_window.child_window(title="姓名:", control_type="Edit", found_index=1)
name_edit.set_text("张三")

email_edit = form_window.child_window(title="电子邮件:", control_type="Edit", found_index=1)
email_edit.set_text("zhangsan@example.com")

# 选择下拉框选项
province_combo = form_window.child_window(title="省份:", control_type="ComboBox")
province_combo.select("北京")

# 选择单选按钮
gender_male = form_window.child_window(title="男", control_type="RadioButton")
gender_male.click()

# 勾选复选框
agreement_check = form_window.child_window(title="我同意服务条款", control_type="CheckBox")
if not agreement_check.is_checked():
    agreement_check.click()

# 提交表单
submit_button = form_window.child_window(title="提交", control_type="Button")
submit_button.click()

3. 处理系统对话框

# 处理文件复制确认对话框
confirm_dialog = app.window(title="确认文件替换")
if confirm_dialog.exists():
    yes_button = confirm_dialog.child_window(title="是", control_type="Button")
    yes_button.click()

# 处理警告对话框
warning_dialog = app.window(title="警告")
if warning_dialog.exists():
    ok_button = warning_dialog.child_window(title="确定", control_type="Button")
    ok_button.click()

# 处理UAC提升权限对话框
uac_dialog = Desktop(backend="uia").window(title="用户帐户控制")
if uac_dialog.exists():
    yes_button = uac_dialog.child_window(title="是", control_type="Button")
    yes_button.click()

4. 复杂控件处理

# 处理日期选择器
date_picker = window.child_window(control_type="DateTimePicker")
date_picker.click_input()  # 打开日期选择器
# 选择月份
month_combo = date_picker.child_window(control_type="ComboBox", found_index=0)
month_combo.select("12月")
# 选择日期
day_button = date_picker.child_window(title="25", control_type="Button")
day_button.click()

# 处理树形控件
tree_view = window.child_window(control_type="Tree")
tree_item = tree_view.get_item(['父节点', '子节点', '叶子节点'])
tree_item.click()

# 处理列表视图
list_view = window.child_window(control_type="List")
list_items = list_view.items()
for item in list_items:
    print(item.texts())

常见问题与解决方案

  1. 找不到窗口或控件

    • 问题:ElementNotFoundError 错误
    • 解决方案:

      • 使用 print(window.dump_tree())print_control_identifiers() 查看控件层次结构
      • 检查控件的标识符是否正确(标题、ID、类名等)
      • 增加等待时间,某些控件可能需要时间加载
      • 尝试使用不同的属性组合来定位控件
  2. 操作过快导致失败

    • 问题:自动化操作太快,应用程序反应不过来
    • 解决方案:

      • 在关键操作之间添加 time.sleep() 适当延时
      • 增加全局等待时间 timings.Timings.slow()
      • 使用 wait()wait_not() 等待控件状态变化
  3. 匹配到多个控件

    • 问题:ElementAmbiguousError 错误
    • 解决方案:

      • 使用 found_index 参数指定具体的控件
      • 添加更多的属性条件缩小匹配范围
      • 使用 children() 获取所有匹配控件,然后遍历处理
  4. 后端选择问题

    • 问题:某些应用程序用一种后端无法正常工作
    • 解决方案:

      • 现代应用通常使用 "uia" 后端:Application(backend="uia")
      • 旧应用程序可能需要 "win32" 后端:Application(backend="win32")
      • 尝试两种后端,看哪种更适合你的应用
  5. 控件识别不稳定

    • 问题:同一个控件有时能找到,有时找不到
    • 解决方案:

      • 使用多个属性结合定位控件,如 titleclass_namecontrol_type 结合
      • 使用 title_re 进行模糊匹配:child_window(title_re=".*保存.*")
      • 先定位父控件,再从父控件中查找子控件
  6. 权限不足

    • 问题:无法操作需要管理员权限的应用
    • 解决方案:

      • 以管理员身份运行 Python 脚本
      • 处理 UAC 对话框:Desktop().window(title="用户帐户控制").child_window(title="是").click()
  7. 文本输入问题

    • 问题:type_keys()set_text() 无法正确输入文本
    • 解决方案:

      • 对于复杂输入,优先使用 type_keys()
      • 对于中文等非 ASCII 字符,确保正确的编码
      • 尝试先清空文本框:edit_box.set_text("") 然后再输入
  8. 菜单操作失败

    • 问题:menu_select() 无法找到或点击菜单项
    • 解决方案:

      • 确认菜单路径是否正确,包括访问键标记如 "(F)"
      • 尝试使用快捷键操作菜单
      • 使用菜单对象直接操作:menu.item_by_path("文件").click()
  9. 动态变化的 UI

    • 问题:UI 元素位置或属性动态变化
    • 解决方案:

      • 使用更稳定的属性(如automation_id)而非位置来定位
      • 实现异常处理和重试机制
      • 使用条件检查确认 UI 状态后再操作
  10. 性能问题

    • 问题:自动化脚本运行缓慢
    • 解决方案:

      • 缓存常用窗口和控件对象,避免重复查找
      • 使用更精确的查找条件加快查找速度
      • 调整 timings 设置,减少不必要的等待时间
      • 对于大型应用程序,使用 wait_cpu_usage_lower() 等待 CPU 使用率降低
  11. 截图失败

    • 问题:capture_as_image() 返回 None 或截图不完整
    • 解决方案:

      • 确保已安装 PIL 库:pip install Pillow
      • 确保窗口在前台且可见
      • 对于部分最小化的窗口,先恢复窗口状态
  12. ComboBox 操作问题

    • 问题:下拉框选择项目失败
    • 解决方案:

      • 使用两步操作:先 click_input() 展开,再选择项目
      • 检查 ComboBox 类型,不同类型的 ComboBox 可能需要不同操作方式

实用技巧

  • 使用 spy++ 或 Windows 自带的 "Inspect" 工具帮助识别窗口和控件属性
  • 使用 print(control.print_control_identifiers()) 打印控件的所有可能标识符
  • 对于复杂的窗口结构,使用 dump_tree() 方法查看完整的控件层次
  • 对于调试,使用 draw_outline() 方法高亮显示找到的控件
  • 捕获异常以处理可能出现的窗口或控件未找到情况
  • 对于性能敏感的应用,尝试使用 app.wait_cpu_usage_lower() 等待 CPU 使用率降低

示例代码

from pywinauto.application import Application
import time

# 启动计算器
app = Application(backend="uia").start("calc.exe")
time.sleep(1)

# 获取主窗口
calc = app.window(title="计算器")

# 点击数字和运算符
calc.child_window(title="1", control_type="Button").click()
calc.child_window(title="加", control_type="Button").click()
calc.child_window(title="2", control_type="Button").click()
calc.child_window(title="等于", control_type="Button").click()

# 获取结果
result = calc.child_window(auto_id="CalculatorResults").texts()[0]
print(f"计算结果: {result}")

参考资源

标签: pywinauto

添加新评论