Python uiautomation模块实战指南
uiautomation
1. 引言
目的: 本指南旨在为想要使用Python进行Windows应用程序自动化的人士提供全面的学习资源。无论你是新手还是有一定经验的开发者,都能从中找到有价值的信息。
概述: uiautomation
是一个强大的Python库,用于与基于Microsoft UI Automation(UIA)框架的应用程序交互。它可以帮助你自动化日常任务、执行软件测试或创建辅助工具。
https://github.com/yinkaisheng/Python-UIAutomation-for-Windows
2. 环境搭建
系统要求:
安装方法: 通过pip安装是最简单的方法:
pip install uiautomation
3. 基础概念
UI Automation: UI Automation是微软提供的技术,允许程序以编程方式访问和操作用户界面元素。uiautomation
库利用了这一技术来实现对Windows应用的控制。
控件树: 每个Windows应用程序都有一个由各种控件组成的层次结构。uiautomation
可以通过遍历这个树状结构来查找特定的控件。
关键术语:
4. uiautomation
常用方法
-
Click() -> None
- 含义:该方法用于模拟鼠标点击操作,通常用于按钮、链接或其他可点击的UI元素。
-
SendKeys() -> None
import uiautomation as uia
uia.Click(x=10, y=10) # 鼠标点击坐标(10, 10)
import uiautomation as uia
uia.SendKeys("xxxx") # 输入文本xxxx
uia.SendKeys("{Ctrl}c") # 按快捷键 Ctrl +c
uia.SendKeys('0123456789{Enter}') # 输入文本0123456789后按Enter键
-
ShowWindow(handle: int, cmdShow: int) -> bool
- 含义:用来显示或隐藏指定的应用程序窗口。此方法不一定能确保窗口被带到最前并且获得焦点
- 参数:
handle
: 控件句柄cmdShow
: 执行的操作,0
、1
、2
、3
、或者4
。-
WaitForExist(control: Control, timeout: float) -> bool
- 含义:等待某个特定的UI元素出现。如果在设定的时间内找到了这个元素,则返回True;否则返回False。
- 参数:
control
: 控件对象timeout
: 等待时间-
WaitForDisappear(control: Control, timeout: float) -> bool
- 含义**:等待某个特定的UI元素消失。如果在设定的时间内该元素确实消失了,则返回True;反之则返回False。
- 参数:
control
: 控件对象timeout
: 等待时间-
ShowDesktop() -> None
- 含义:模拟按下“显示桌面”的功能,最小化所有打开的窗口以展示桌面。
-
GetRootControl() -> Control
- 含义:获取当前桌面的根控件对象。这通常是整个屏幕或者最顶层窗口的对象,从这里可以遍历找到其他子控件。
-
GetScreenSize() -> Tuple[int, int]:
- 含义:返回屏幕的尺寸大小。
window = uia.WindowControl(SubName="PN")
print(window.NativeWindowHandle) #返回控件句柄
uia.ShowWindow(window.NativeWindowHandle, 4) # 以最近一次的大小和状态显示窗口
uia.ShowWindow(window.NativeWindowHandle, 3) # 将窗口最大化
uia.ShowWindow(window.NativeWindowHandle, 2) # 将窗口最小化
uia.ShowWindow(window.NativeWindowHandle, 1) # 将窗口窗口化
uia.ShowWindow(window.NativeWindowHandle, 0) # 隐藏窗口
import uiautomation as uia
window = uia.WindowControl(SubName="PN")
print(uia.WaitForExist(window, 10)) # 10s内等待window窗口出现
import uiautomation as uia
window = uia.WindowControl(SubName="PN")
print(uia.WaitForDisappear(window, 10)) # 10s内等待window窗口消失
import uiautomation as uia
uia.ShowDesktop()
import uiautomation as uia
root = uia.GetRootControl() # 桌面控件
for con in root.GetChildren():
print(con)
import uiautomation as uia
print(uia.GetScreenSize())
5. Control
常用类型
类型 | 控件解释 |
---|---|
WindowControl | 程序窗口 |
ButtonControl | 按钮 |
TextControl | 文本显示 |
EditControl | 编辑框 |
MenuControl | 菜单 |
MenuItemControl | 菜单选项 |
RadioButtonControl | 单选框 |
CheckBoxControl | 勾选框 |
6. Control
常用属性
- Name(self) -> str
- 含义:控件的名称或标签文本。
- 用途:用于标识和查找控件。
- ControlTypeName(self) -> str
- 含义:控件类型名称,如按钮、编辑框、窗口等。
- 用途:帮助区分不同类型的控件。
- ClassName(self) -> str
- 含义:控件的类名,通常与控件的具体实现有关。
- 用途:辅助查找特定类型的控件。
- AutomationId(self) -> str
- 含义:控件的自动化ID,是一个唯一的标识符。
- 用途:用于更精确地定位控件。
- BoundingRectangle(self) -> Rect
- 含义:控件在屏幕上的边界矩形,包含
left
,top
,right
,bottom
属性。 - 用途:获取控件的位置和尺寸,用于点击、截图等操作。
- IsEnabled(self) -> bool
- 含义:控件是否启用(可交互)。
- 用途:判断控件是否可以被用户操作。
- IsOffscreen (self) -> bool
- 含义:控件是否位于屏幕之外(不可见)。
- 用途:检查控件是否超出当前视图范围。
7. Control
常用方法及解释
-
Exists(self) -> bool
- 含义:检查控件是否存在于UI树中。
- 参数:
maxSearchSeconds
: 可选的超时时间(秒),用于等待控件出现,默认为5。- 用途:确保在执行其他操作之前,控件确实存在。
-
Click(self) -> None
- 含义:模拟鼠标点击控件。
- 参数
ratioX
,ratioY
: 点击位置相对于控件宽度和高度的比例(默认为中心)。x
,y
: 绝对坐标(像素),相对于控件左上角的位置。simulateMove
: 这个布尔值参数决定了在执行点击动作时,鼠标指针移动到目标坐标点的方式- 用途:用于触发按钮点击或其他需要鼠标交互的操作。
-
DoubleClick(self) -> None
- 含义:模拟双击控件。
- 参数:同
Click()
方法。 - 用途:用于需要双击激活的功能。
-
RightClick(self) -> None)
- 含义:模拟右键点击控件。
- 参数:同
Click()
方法。 - 用途:用于调出上下文菜单等操作。
-
DragDrop(self) -> None
- 含义:拖放控键,可以用来模拟用户移动窗口,或者拖拽窗口大小动作。
- 参数:
x1
,y1
: 点击控件的绝对位置。x2
,y2
: 释放目标坐标的绝对位置。moveSpeed
: 移动速度(秒)。- 用途:用于实现拖拽操作。
-
SendKeys(self) -> None
- 含义:向控件发送键盘输入。可以发送文本,也可以发送按键
- 参数:
text
: 要发送的字符串或按键组合(例如'{CTRL}a'
表示Ctrl+A,)。interval
:浮点数,按键之间的秒数。waitTime
: 发送后等待的时间(秒)。- 用途:用于文本输入或发送快捷键命令。
-
SetFocus(self) -> bool
- 含义:将焦点设置到该控件上。
- 用途:确保后续操作针对正确的目标控件。
-
SetActive(self) -> bool
- 含义:将窗口设为前台活动窗口。
- 用途:使窗口成为当前活动窗口,以便接收输入。
-
SetTopMost(filename)
- 含义:置顶窗口。
- 用途:置顶目标窗口,便于操作该窗口。
-
GetTopLevelControl(self) -> Optional[‘Control’]
- 含义:获取当前控件所放置的顶级控件。
- 用途:获取最前面的控件。
8. Pattern
常用接口
Pattern 类提供了对控件行为的访问,例如滚动、选择、值变化等。每个模式都对应着一组方法、属性和事件,它们描述了控件能够执行的操作。以下是一些常见的 UIAutomation 模式:
1. InvokePattern
含义: InvokePattern
通常用于模拟用户对控件的“调用”或“激活”操作。当用户点击按钮时,他们实际上是在“调用”该按钮所代表的功能。在自动化测试或者辅助技术中,InvokePattern
允许开发者通过代码来触发这些相同的用户交互行为。
常用方法
Invoke(self, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 用于点击支持InvokePattern的控件
用法:
import uiautomation as uia
target_control = uia.ButtonControl()
target_control.GetInvokePattern().Invoke()
2. SelectionPattern
含义:SelectionPattern
提供了对多选或多选项目选择状态的访问。这个模式允许自动化客户端控制支持选择功能的控件(如列表框、组合框、树形控件等),并可以获取当前的选择信息。
常用方法
GetSelection(self) -> List[‘Control’]
用途: 用于获取已选择的控件列表
用法:
target_control = uia.ComboBoxControl()
print(target_control.GetSelectionPattern().GetSelection())
3. ValuePattern
含义:ValuePattern
允许获取或设置支持该模式的控件的值。例如,它可以用于文本框、滑块、旋钮等控件来读取当前值或者设置新值。
常用属性
IsReadOnly(self) -> bool
用途: 判断该值是否为只读(即用户不能修改
用法:
target_control = uia.DocumentControl(Name="文本编辑器")
print(target_control.GetValuePattern().IsReadOnly)
Value(self) -> str
用途: 获取控件的当前值
用法:
target_control = uia.DocumentControl(Name="文本编辑器")
print(target_control.GetValuePattern().Value)
常用方法
SetValue(self, value: str, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 设置控件的新值
用法:
target_control = uia.DocumentControl(Name="文本编辑器")
print(target_control.GetValuePattern().SetValue("New Text"))
4. ExpandCollapsePattern
含义: ExpandCollapsePattern
是 UIAutomation 框架中的一个模式,它允许控制和获取支持展开或折叠行为的控件的状态。例如,它可以用于树节点、菜单项、组合框等控件来展开或折叠它们的内容。
常用属性
ExpandCollapseState(self) -> int
用途: 获取当前控件的展开或折叠状态。
用法:
target_control = uia.TreeItemControl(Name="电池")
print(target_control.GetExpandCollapsePattern().ExpandCollapseState)
常用方法
Collapse(self, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 将当前控件设置为折叠状态。
用法:
target_control = uia.TreeItemControl(Name="电池")
target_control.GetExpandCollapsePattern().Collapse()
Expand(self, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 将当前控件设置为展开状态。
用法:
target_control = uia.TreeItemControl(Name="电池")
target_control.GetExpandCollapsePattern().Expand()
5. TogglePattern
含义: TogglePattern
,它允许获取和设置支持切换状态的控件的状态。此模式适用于可以在这几种状态之间切换的控件,例如复选框、开关(toggle switches)、单选按钮等。
常用属性:
ToggleState(self) -> int
用途: 获取当前控件勾选状态。
用法:
target_control = uia.CheckBoxControl(Name="Install")
print(target_control.GetTogglePattern().ToggleState)
常用方法:
Toggle(self, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 切换控件勾选状态。
用法:
target_control = uia.CheckBoxControl(Name="Install")
target_control.GetTogglePattern().Toggle() # 切换状态
6. TextPattern
含义:TextPattern
提供了对文本控件的访问和操作。这个模式允许自动化客户端读取、编辑和导航文本内容,适用于支持丰富文本编辑功能的控件,如文本框、富文本编辑器等。
常用属性
DocumentRange(self) -> TextRange
用途: 获取表示整个文档范围的 TextRange
对象。
返回值: 返回一个 TextRange
对象,代表整个文档的内容。
用法:
target_control = uia.DocumentControl()
print(target_control.GetTextPattern().DocumentRange.GetText())
7. WindowPattern
WindowPattern
是 UIAutomation 框架中的一个模式,它提供了对窗口控件的控制。通过 WindowPattern
,自动化客户端可以获取窗口的状态信息、最大化、最小化、关闭窗口以及设置窗口的可见性。
常用属性
CanMaximize(self) -> bool
用途: 获取当前窗口是否支持最大化。
返回值: 如果窗口支持最大化,则返回 True
;否则返回 False
。
用法:
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().CanMaximize)
CanMinimize(self) -> bool
用途: 获取当前窗口是否支持最小化。
返回值: 如果窗口支持最小化,则返回 True
;否则返回 False
。
用法
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().CanMinimize)
IsModal(self) -> bool
用途: 获取当前窗口是否为模态窗口(即在该窗口关闭之前用户无法与其它窗口交互)。
返回值: 如果窗口是模态窗口,则返回 True
;否则返回 False
。
用法
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().IsModal)
IsTopmost(self) -> bool
用途: 获取当前窗口是否始终位于顶层。
返回值: 如果窗口始终位于顶层,则返回 True
;否则返回 False
。
用法
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().IsTopmost)
WindowVisualState(self) -> int
用途: 获取当前窗口的视觉状态。
返回值:
0
: 窗口处于正常状态。1
: 窗口已最大化。2
: 窗口已最小化。用法
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().WindowVisualState)
常用方法
Close(self, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 发送关闭请求给窗口。
返回值: 返回 True
表示操作成功,否则返回 False
。
用法:
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().Close())
SetWindowVisualState(self, state: int, waitTime: float = OPERATION_WAIT_TIME) -> bool
用途: 设置窗口的视觉状态(最大化、最小化、恢复正常),将窗口带到最前并且获得焦点,。
参数:
state
: 新的窗口视觉状态,取值为 0
, 1
, 或 2
。返回值: 返回 True
表示操作成功,否则返回 False
。
用法:
target_control = uia.WindowControl(SubName="设备管理器")
print(target_control.GetWindowPattern().SetWindowVisualState(0)) # 窗口化
print(target_control.GetWindowPattern().SetWindowVisualState(1)) # 最大化
print(target_control.GetWindowPattern().SetWindowVisualState(2)) # 最小化
9. 常用的点击方法
uiautomation.Click() # 单击控件
uiautomation.DoubleClick() # 双击控件
uiautomation.RightClick() # 单击右键
uiautomation.MiddleClick() # 点击鼠标中键(滚轮)
上面的点击方法默认点击控件的中心点,如果点击控件的其他位置可以传递不同的参数
x
: int
含义:x 是整数,表示点击点相对于控件左右侧的绝对坐标(以像素为单位)。
如果x>0, 表示横向方向上的偏移量,从控件最左侧边缘开始计算,向右偏移
如果x<0, 表示横向方向上的偏移量,从控件最右侧边缘开始计算,向左偏移
y
: int
含义:y 是整数,表示点击点相对于控件上下侧的绝对坐标(以像素为单位)。
如果y>0, 表示纵向方向上的偏移量,从控件最上侧边缘开始计算,向下偏移
如果y<0, 表示纵向方向上的偏移量,从控件最下侧边缘开始计算,向上偏移
ratioX
: float
含义:ratioX 是一个浮点数,表示点击点相对于控件宽度的比例位置。它的取值范围是 0 到 1。
0 表示点击点位于控件的最左侧边缘。
1 表示点击点位于控件的最右侧边缘。
0.5(默认值)表示点击点位于控件的水平中心。
ratioY
: float
含义:ratioY 是一个浮点数,表示点击点相对于控件高度的比例位置。它的取值范围是 0 到 1。
0 表示点击点位于控件的最顶侧边缘。
1 表示点击点位于控件的最底侧侧边缘。
0.5(默认值)表示点击点位于控件的水平中心。
simulateMove
: bool
含义: 这个布尔值参数决定了在执行点击动作时,鼠标指针移动到目标坐标点的方式。
false:
当设置为 false 时,鼠标指针会直接瞬间移动到指定的目标坐标点。
用户将不会看到鼠标指针的移动轨迹,仿佛鼠标指针“瞬移”到了新位置。
这种方式通常更快,但缺乏自然的交互感觉,因为真实的用户操作一般会有移动的过程。
true:
如果设置为 true,则鼠标指针会从当前坐标点开始,缓慢且平滑地移动到目标坐标点。
用户能够看到鼠标指针的移动路径,模拟了更自然的人机交互过程。
这种方式虽然可能稍微慢一些,但它提供了更加真实的用户体验,并且对于某些自动化测试场景来说,可以更好地模拟实际用户的操作行为。
10. 使用 uiautomation 常见问题
问题 1: 权限问题
描述:在某些情况下,uiautomation
可能需要管理员权限才能正常工作,尤其是在枚举控件或获取控件信息时。
解决方案:
管理员权限运行: 在 Windows 系统中,右键点击 Python 解释器或 IDE(如 PyCharm、VSCode),选择“以管理员身份运行”。
打包exe时添加管理员权限:
# 使用命令打包对应的主程序,生成xxx.spec文件
pyinstaller -F --uac-admin xxx.py
# 使用以下命令完成打包
pyinstaller xxx.spec
问题 2: 控件不在可见区域内,无法直接点击
描述:尝试对一个不在当前可见区域内的控件(如滚动窗口外的按钮)进行点击时,可能会失败,因为该控件没有被渲染到屏幕上。
解决方案:
显示目标控件并点击: 尝试将目标控件显示在视图中,然后点击
target_control = uia.HyperlinkControl(Name="获取帮助")
if target_control.Exists():
for _ in range(10):
if not target_control.IsOffscreen: # 判断是否在视图中
target_control.Click()
break
target_control.SendKeys("{PageDown}") # 通过PageDown按钮来滚动页面
直接发送按钮: 如果该控件可以通过按键的方式打开, 例如"Enter"
target_control = uia.HyperlinkControl(Name="获取帮助")
if target_control.Exists():
target_control.SendKeys("{Enter}")
使用 Invoke 模式:如果控件支持 InvokePattern
,可以直接调用 Invoke()
方法而不必关心它的可见性。
target_control = uia.HyperlinkControl(Name="获取帮助")
if target_control.Exists():
target_control.GetInvokePattern().Invoke()
问题 3: 查找过程中抛异常LookupError: Find Control Timeout(10s)
描述:当使用 uiautomation
库进行控件查找时遇到 LookupError: Find Control Timeout(10s)
错误,这意味着在默认的超时时间内(通常是10秒),库未能找到指定的控件。这个问题可能由多种因素引起,包括但不限于页面加载延迟、控件属性不准确或动态变化、应用程序响应速度慢等。
解决方案:
增加判断机制: 通过Exists()方法判断控件是否存在,存在后再进行操作
target_control = uia.HyperlinkControl(Name="获取帮助")
if target_control.Exists():
target_control.GetInvokePattern().Invoke()
增强稳定性:捕获异常并添加重试机制来提高脚本的稳定性
for _ in range(3):
try:
target_control = uia.HyperlinkControl(Name="获取帮助")
if target_control.Exists(1):
target_control.Click()
break
except Exception as e:
print(e)
问题 4: 浮层或模态对话框遮挡目标控件
描述:有时其他元素(如浮动广告、提示框)会暂时覆盖住你需要操作的目标控件。
解决方案:
关闭或移除障碍物:查找并关闭任何可能遮挡目标控件的弹出窗口或浮动层。
modal_dialog = WindowControl(Name="Modal Dialog")
if modal_dialog.Exists():
close_button = modal_dialog.ButtonControl(Name="Close")
if close_button.Exists():
close_button.Click()
调整窗口层级:确保目标窗口处于最顶层,避免被其他窗口遮挡。
main_window = WindowControl(Name="Main Application Window")
# 通过SetTopmost方法置顶窗口
main_window.SetTopmost方法置顶窗口(True)
11. UIAutomationInitializerInThread
with uia.UIAutomationInitializerInThread():
是 Python 中使用 uiautomation
库时的一种上下文管理器(context manager),用于初始化和清理 UI Automation 环境。这个库主要用于自动化 Windows GUI 测试或与 Windows 应用程序进行交互。
作用
- 线程安全初始化:
UIAutomationInitializerInThread
确保在多线程环境中正确初始化 COM (Component Object Model) 库,特别是为了支持 UI Automation。这是因为 UI Automation API 需要一个特定的 COM 线程模型来工作,通常是在 STA (Single-Threaded Apartment) 模式下。 - 资源管理:通过上下文管理器的方式,它确保了即使发生异常,也能正确地释放分配给 UI Automation 的资源。当代码块执行完毕或者遇到异常时,
__exit__
方法会被调用,从而自动清理并终止 COM 初始化。 - 简化代码:这种方式使得开发者不需要手动管理 COM 的初始化和清理,减少了代码复杂度,并且降低了出错的可能性。
使用示例
import uiautomation as uia
# 正确使用上下文管理器来确保线程安全的初始化和清理
with uia.UIAutomationInitializerInThread():
# 在这里放置所有需要使用 UIAutomation 的代码
window = uia.WindowControl(searchDepth=1, Name='Your Application Window')
window.SetActive()
button = window.ButtonControl(Name='Button Name')
button.Click()
# 当退出 with 语句块后,COM 环境会自动被清理
注意事项
UIAutomationInitializerInThread
上下文中的线程再次进入另一个 UIAutomationInitializerInThread
上下文,因为这可能会导致 COM 初始化问题。UIAutomationInitializerInThread
实例,以维持 COM 的正确初始化状态。总之,with uia.UIAutomationInitializerInThread():
提供了一种方便、安全的方法来处理 UI Automation 的初始化和资源管理,特别是在多线程环境下。它有助于确保应用程序稳定性和减少开发者的负担。
12. uiautomation封装方法
1. 获取所有顶级窗口
import uiautomation as uia
def get_all_windows():
"""
获取所有顶级窗口。
Returns:
list: 包含所有顶层窗口控件对象的列表。
"""
# 使用uiautomation库获取根控件(桌面)
root = uia.GetRootControl()
# 查找所有具有名称属性的顶级窗口
return [window for window in root.GetChildren() if window.Name]
2. 通过名称列表查找多个窗口
def find_windows_by_name(names: (list, str), all_windows=None, identical: bool = False):
"""
通过名称列表查找多个窗口
Args:
names (list of str): 窗口的名称或标题列表。
all_windows (list, 可选): 根控件下的所有控件列表,默认为None将调用get_all_windows获取。
identical (bool): 是否需要完全匹配名称,默认False表示部分匹配。
Returns:
list: 匹配条件的窗口控件列表。
"""
if all_windows is None:
all_windows = get_all_windows()
names = names if isinstance(names, list) else [names]
def match_func(window):
window_name_lower = window.Name.lower() if window.Name else ''
for name in names:
name_lower = name.lower()
if identical and window.Name == name:
return True
elif not identical and name_lower in window_name_lower:
return True
return False
return [window for window in all_windows if match_func(window)]
3. 等待指定名称的窗口出现。
def wait_windows_by_name(names: (list, str), timeout: float = 10, poll_interval: float = 0.5, identical: bool = False):
"""
等待指定名称的窗口出现。
Args:
names (list of str): 窗口的名称或标题列表。
timeout (float): 等待窗口出现的最大秒数,默认为10秒。
poll_interval (float): 每次检查之间的间隔秒数,默认为0.5秒。
identical (bool): 是否需要完全匹配名称,默认False表示部分匹配。
Returns:
list: 匹配条件的窗口控件列表。
"""
end_time = time.time() + timeout
while time.time() < end_time:
try:
# 尝试查找窗口
windows = find_windows_by_name(names, identical=identical)
if windows:
return windows
# 如果没有找到,等待一段时间再重试
time.sleep(poll_interval)
except Exception as e:
logger.error(e)
# 超时后处理
return []
作者:Midway-Z