GUI自动化之pywinauto
目录
一、说明
二、安装pywinauto
三、安装UI对象检查⼯具
四、pywinauto常见操作
1. 打开程序
2. 定位窗口
3. 窗口操作
4. 定位控件
4.1 best_match来定位
4.2 child_window()定位
5. 等待
5.1 wait/wait_not()
5.2 wait_until()
6. 控件的操作
6.1 点击
6.2 文本
7. 鼠标操作
8. 键盘操作
8.1 输入文本
8.2 按键输入
9. 菜单控件的操作
10. 列表控件的操作
一、说明
Pywinauto是⼀款基于Python的跨平台GUI⾃动化库,专⻔针对Windows桌⾯应⽤程序设计,其核⼼能⼒在于通过模拟⽤⼾交互⾏为(如⿏标点击、键盘输⼊)实现对窗⼝、对话框及内部控件的精准定 位与操作,适⽤于⾃动化测试、批量任务处理及⽇常办公流程优化等场景。 该库通过两种底层技术(backend="win32"和backend="uia")适配不同框架开发的应⽤程序:
-
win32模式适⽤于传统MFC、VB6等旧架构
-
uia模式则⽀持现代WinForms、WPF、Qt5及浏览器等应⽤
pywinauto的局限性
仅⽀持Windows:⽆法⽤于Mac或Linux平台。
对⾮标准控件⽀持有限:某些⾃定义控件可能需要额外处理。
注意:pywinauto⽀持传统Windows原⽣应⽤框架和部分跨平台框架(需在Windows运⾏),以下 是⼀些⽆法使⽤pywinauto 实现GUI⾃动化的应⽤程序分类,以及原因
1. 基于Web的应⽤程序
-
原因: pywinauto 是为本地Windows应⽤程序设计的,⽆法直接与基于浏览器的Web应⽤程序交互。Web应⽤程序的⾃动化通常需要使⽤如Selenium这样的⼯具 。
-
⽰例:GoogleChrome中打开的任何⽹⻚或基于Web的企业应⽤。
2. 基于⾃定义渲染引擎的应⽤程序
-
原因:某些应⽤程序(如使⽤Flutter、ReactNative或其他跨平台框架开发的应⽤)可能使⽤ ⾃定义的渲染引擎,这些引擎不会暴露标准的WindowsUI⾃动化接口(如Win32或UIA)。
-
⽰例:企业微信、某些使⽤Flutter开发的桌⾯应⽤。
二、安装pywinauto
安装pywinauto
pip install pywinauto
安装完成后,可以通过以下命令验证安装:
pip list
如果输出中包含pywinauto,则说明安装成功。
三、安装UI对象检查⼯具
在讲解selenium实现web⾃动化时,定位⻚⾯的元素是必须要的,如何进⾏元素定位?打开⻚⾯开发 者⼯具,可以直接进⾏元素定位,定位到元素之后才能对指定元素进⾏⼀系列的操作,对于GUI⾃动化 也是⼀样,要想对界⾯的元素进⾏操作,我们得先会“找元素”,⽽找元素我们需要⽤UI对象检查⼯ 具, UI对象检查⼯具有很多,诸如Spy++、Inspect.exe、UISpy,这些⼯具基本都⻓得⼀样,且操作上基 本⽆差,我使⽤UISpy⼯具来进⾏UI对象检查。
UISpy是⼀款由微软提供的⼯具,专⻔⽤于UI⾃动化测试。它允许测试⼈员查看应⽤程序的UI⾃动 化模型,包括控件树、属性和事件。UISpy特别适⽤于编写UI⾃动化脚本,因为它提供了⼀个可视 化的界⾯来帮助测试⼈员选择UI元素,并且可以模拟⽤⼾对这些UI元素的操作。
四、pywinauto常见操作
1. 打开程序
我们要控制软件的第一件事就是启动一个Windows软件,每一个软件(进程)都是一个Application对象
实例化Application对象的时候可以传入一个backend参数,可选值为win32(默认)和uia
-
win32对应的框架:MFC、VB6、VCL、简单的 WinForms 控件和大多数旧的遗留应用程序
-
uia对应的框架:WinForms、WPF、商店应用程序、Qt5、浏览器
如果无法知道要测试的软件是属于哪种框架,可以使用 Inspect(对应uia) 和 Spy++(对应win32)看看,你看哪个显示得更全就选哪个。Inspect和Spy++需要自己安装一下
下面是Application对象的主要方法
方法 | 常用参数 | 说明 |
---|---|---|
start() | cmd_line、timeout、retry_interval | 通过cmd命令启动一个软件(进程) |
connect() | process、handle、path、timeout | 连接一个进程,一般是使用进程号(任务管理器可以看到) |
打开程序
Application(backend="uia").start("D:\应用商店下载安装的地方\WPS Office\ksolaunch.exe")
-
cmd_line:这是启动应⽤程序的命令⾏字符串。它必须包含应⽤程序的路径和名称,还可以包含启 动参数(绝对路径)
连接已经打开的应用程序
#获取应⽤程序对应的pid
#app = Application(backend="uia").connect(process=app.process)
app = Application(backend="uia").connect(process=31528)#通过句柄连接
app = Application(backend="uia").connect(handle=65552)
-
process :⽬标的进程ID
-
handle :⽬标的窗⼝句柄
注意:连接到已在运⾏的进程,该动作仅根据⼀个参数执⾏
2. 定位窗口
方法 | 常用参数 | 说明 |
---|---|---|
window() | title、title_re、class_name、best_match | 获取单个窗口(WindowSpecification) |
app.window(title='',...)
app是start或connect返回到对象
-
title :⽂本为指定值的元素。
-
title_re :⽂本匹配指定正则表达式的元素。
-
best_match :标题与指定值相似的元素。
-
class_name :窗⼝类为指定值的元素。
-
class_name_re :类名匹配指定正则表达式的元素。
import time
from pywinauto.application import Application
from pywinauto import Application# 连接到一个应用程序
app = Application(backend="uia").start("D:\应用商店下载安装的地方\wangyiweidaofanyi\YoudaoDict.exe")
time.sleep(5)# 获取主窗口
#title--精确匹配
main_window = app.window(title="网易有道翻译")# title_re-正则匹配
main_window = app.window(title_re=".*易有道翻*.")#class_name--精确匹配
main_window = app.window(class_name="YodaoMainWndClass")#class_name--正则匹配
main_window = app.window(class_name_re=".*MainWndClass")#best_match-模糊匹配
main_window = app.window(best_match="网易有道翻译Pane")# 打印所有控件标识符
main_window.print_control_identifiers()
3. 窗口操作
⽅法 | 功能描述 | 返回值 |
close() | 关闭窗口 | ⽆返回值 |
maximize() | 将窗⼝最⼤化 | |
minimize () | 最⼩化窗⼝ | |
restore() | 窗⼝恢复正常⼤⼩ | |
get_show_state() | 获取窗⼝的显⽰状态 | 返回⼀个整数值,表⽰窗⼝的状态: 0:正常 1:最⼤化 2:最⼩化 |
is_dialog() | 检查控件是否是顶级窗口 | 返回布尔值: True 表⽰是顶级窗口, False 表⽰ 不是顶级窗口 |
is_maximized() | 检查窗⼝是否处于最⼤化状态 | 返回布尔值: True 表⽰窗口已最⼤化, False 表 ⽰未最⼤化 |
is_minimized() | 检查窗⼝是否处于最⼩化状态 | 返回布尔值: True 表⽰窗⼝已最⼩化, False 表 ⽰未最⼩化 |
is_normal() | 检查窗⼝是否处于正常状 态(既未最⼩化也未最⼤ 化) | 返回布尔值: True 表⽰窗⼝处于正常状态, False 表⽰未处于正常状态 |
import time
from pywinauto import Application# 连接到一个记事本
app = Application(backend="uia").connect(process=56372)
time.sleep(3)
# 获取主窗口
main_window = app.window(class_name="Notepad")
# 等待
main_window.wait('exists')
# 最大化
main_window.maximize()
print("is_minimized:",main_window.is_minimized())
# 最小化
main_window.minimize()
print("is_maximized:",main_window.is_maximized())
# 复原
main_window.restore()
print("is_normal:",main_window.is_normal())
# 获取窗⼝的显⽰状态
print("get_show_state:",main_window.get_show_state())
# 检查控件是否是顶级窗口
print("is_dialog",main_window.is_dialog())
# 关闭
main_window.close()
4. 定位控件
在使⽤pywinauto进行自动化操作时,程序启动后,首先需要定位到目标窗口。窗口是所有控件的容 器,只有成功定位到窗口,才能进⼀步操作窗口内的控件。控件是窗口功能的具体体现,包括按钮、 文本框、列表等,对控件的操作是自动化任务的核心。然而,在对控件进行操作之前,必须先完成控 件的定位。定位控件是确保操作准确性的关键步骤,也是自动化流程的基础。
常见控件示例:
分类 | 控件名称 | 说明 |
窗⼝与对话框 | 对话框(Dialog) | ⽤于与⽤⼾交互,如显⽰警告、确认操作或输⼊信息。 |
窗格(Pane) | 通常作为窗⼝的⼀部分,⽤于显⽰特定内容或功能。 | |
输⼊控件 | 按钮(Button) | ⽤于触发操作,如点击按钮执⾏某个功能。 |
编辑栏(Edit) | ⽤于输⼊或编辑⽂本,⽀持多⾏或单⾏输⼊。 | |
组合框(ComboBox) | 结合⽂本框和列表框,⽤⼾可以选择预定义选项或输⼊⾃ 定义值。 | |
列表框(ListBox) | ⽤于显⽰可选择的项⽬列表,⽀持单选或多选。 | |
菜单控件 | 菜单(Menu) | ⽤于提供功能选项,通常位于窗⼝顶部。 |
菜单项(MenuItem) | 菜单中的具体选项,点击后执⾏对应功能。 | |
弹出菜单(PopupMenu) | 右键单击时弹出的菜单,⽤于快速访问常⽤功能。 | |
列表显⽰控件 | 列表显⽰控件(ListView) | ⽤于以表格形式显⽰数据,⽀持多列显⽰和排序。 |
容器控件 | 组框(GroupBox) | ⽤于对控件进⾏分组,提⾼界⾯的可读性和组织性。 |
选择控件 | 复选框(CheckBox) | ⽤于多选操作,⽤⼾可以勾选多个选项。 |
单选框(RadioButton) | ⽤于互斥选择,⽤⼾只能选择⼀个选项。 | |
显⽰控件 | 状态栏(StatusBar) | 通常位于窗⼝底部,⽤于显⽰应⽤状态或提⽰信息。 |
静态内容(Static) | ⽤于显⽰静态⽂本或图像,通常不可编辑。 | |
导航控件 | 树状视图(TreeView) | ⽤于展⽰分层数据,如⽂件夹结构或组织结构。 |
选项卡控件(TabControl) | ⽤于在有限空间内组织多个⻚⾯或选项卡,每个选项卡可 以包含不同的内容。 | |
⼯具控件 | ⼯具栏(Toolbar) | ⽤于放置常⽤按钮或⼯具,⽅便⽤⼾快速操作。 |
⼯具提⽰(ToolTips) | 当⿏标悬停在控件上时显⽰提⽰信息,帮助⽤⼾理解控件 功能。 | |
头部内容 | 头部内容(Header) | 通常⽤于显⽰标题或表头信息,如表格的列标题。 |
定位控件,需要借助前面学到的 print_control_identifiers() ⽅法,打印窗口及其子控件的标识符信息
import time
from pywinauto import Application# 连接到一个记事本
app = Application(backend="uia").connect(process=28304)
time.sleep(3)
# 获取主窗口
main_window = app.window(class_name="Notepad")
# 等待
main_window.wait('exists')
# 打印所有控件标识符
main_window.print_control_identifiers()
Dialog - '新建 文本文档.txt - Notepad' (L733, T131, R1601, B985)
['Dialog', '新建 文本文档.txt - Notepad', '新建 文本文档.txt - NotepadDialog', 'Dialog0', 'Dialog1']
child_window(title="新建 文本文档.txt - Notepad", control_type="Window")| | Pane - '' (L741, T224, R1593, B937)| ['Pane', '新建 文本文档.txtPane', 'Pane0', 'Pane1', '新建 文本文档.txtPane0', '新建 文本文档.txtPane1']| | | | Document - '' (L741, T224, R1593, B937)| | ['新建 文本文档.txtDocument', 'Document']| | Pane - '' (L791, T143, R1150, B183)| ['Pane2', '新建 文本文档.txtPane2']| | | | Pane - '' (L791, T143, R1150, B183)| | ['Pane3', '新建 文本文档.txtPane3']| | | | | | TabControl - '' (L791, T143, R1150, B183)| | | ['TabControl', 'TabControl添加新标签页', '新建 文本文档.txtTabControl']| | | child_window(auto_id="Tabs", control_type="Tab")| | | | | | | | ListBox - '' (L793, T143, R1103, B183)| | | | ['ListBox', '新建 文本文档.txtListBox']| | | | child_window(auto_id="TabListView", control_type="List")| | | | | | | | | | TabItem - '新建 文本文档.txt. 未修改。' (L796, T143, R1103, B183)| | | | | ['TabItem', '新建 文本文档.txt. 未修改。TabItem', '新建 文本文档.txt. 未修改。']| | | | | child_window(title="新建 文本文档.txt. 未修改。", control_type="TabItem")| | | | | | | | | | | | Static - '新建 文本文档.txt' (L812, T153, R930, B172)| | | | | | ['新建 文本文档.txtStatic', 'Static', '新建 文本文档.txt', 'Static0', 'Static1']| | | | | | child_window(title="新建 文本文档.txt", control_type="Text")| | | | | | | | | | | | Button - '关闭标签页' (L1052, T148, R1092, B178)| | | | | | ['关闭标签页Button', '关闭标签页', 'Button', 'Button0', 'Button1']| | | | | | child_window(title="关闭标签页", auto_id="CloseButton", control_type="Button")| | | | | | | | Button - '添加新标签页' (L1107, T149, R1147, B179)| | | | ['添加新标签页Button', 'Button2', '添加新标签页']| | | | child_window(title="添加新标签页", auto_id="AddButton", control_type="Button")| | | | | | Pane - '记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。' (L0, T0, R0, B0)| | | ['Pane4', '记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。', '记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。Pane']| | | child_window(title="记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。", auto_id="TeachingTip", control_type="Pane")| | Pane - '' (L741, T183, R1593, B224)| ['Pane5', '新建 文本文档.txtPane4']| | | | Pane - '' (L741, T183, R1593, B224)| | ['Pane6', '新建 文本文档.txtPane5']| | | | | | Menu - '' (L741, T183, R951, B223)| | | ['Menu', '新建 文本文档.txtMenu', 'Menu0', 'Menu1']| | | child_window(auto_id="MenuBar", control_type="MenuBar")| | | | | | | | MenuItem - '文件' (L746, T183, R806, B223)| | | | ['MenuItem', '文件MenuItem', '文件', 'MenuItem0', 'MenuItem1']| | | | child_window(title="文件", auto_id="File", control_type="MenuItem")| | | | | | | | MenuItem - '编辑' (L816, T183, R876, B223)| | | | ['MenuItem2', '编辑MenuItem', '编辑']| | | | child_window(title="编辑", auto_id="Edit", control_type="MenuItem")| | | | | | | | MenuItem - '查看' (L886, T183, R946, B223)| | | | ['查看', 'MenuItem3', '查看MenuItem']| | | | child_window(title="查看", auto_id="View", control_type="MenuItem")| | | | | | Button - '标题' (L1016, T183, R1087, B223)| | | ['标题', 'Button3', '标题Button']| | | child_window(title="标题", control_type="Button")| | | | | | Button - '列表' (L1087, T183, R1157, B223)| | | ['列表', 'Button4', '列表Button']| | | child_window(title="列表", control_type="Button")| | | | | | Button - '加粗(Ctrl+B)' (L1158, T183, R1198, B223)| | | ['加粗(Ctrl+B)', '加粗(Ctrl+B)Button', 'Button5']| | | child_window(title="加粗(Ctrl+B)", control_type="Button")| | | | | | Button - '斜体(Ctrl+I)' (L1198, T183, R1238, B223)| | | ['斜体(Ctrl+I)', '斜体(Ctrl+I)Button', 'Button6']| | | child_window(title="斜体(Ctrl+I)", control_type="Button")| | | | | | Button - '链接(Ctrl+K)' (L1238, T183, R1278, B223)| | | ['链接(Ctrl+K)Button', '链接(Ctrl+K)', 'Button7']| | | child_window(title="链接(Ctrl+K)", control_type="Button")| | | | | | Button - '清除格式设置(Ctrl+空格)' (L1278, T183, R1318, B223)| | | ['清除格式设置(Ctrl+空格)', '清除格式设置(Ctrl+空格)Button', 'Button8']| | | child_window(title="清除格式设置(Ctrl+空格)", control_type="Button")| | | | | | Button - '设置' (L1545, T183, R1583, B223)| | | ['设置', 'Button9', '设置Button']| | | child_window(title="设置", auto_id="SettingsButton", control_type="Button")| | | | | | Dialog - '' (L0, T0, R0, B0)| | | ['Dialog2']| | | child_window(auto_id="PrivacyTeachingTip", control_type="Window")| | | | | | Dialog - '' (L0, T0, R0, B0)| | | ['Dialog3']| | | child_window(auto_id="CowriterTeachingTip", control_type="Window")| | Pane - '' (L741, T937, R1593, B977)| ['Pane7', ' 行 1,列 1Pane', ' 行 1,列 1Pane0', ' 行 1,列 1Pane1']| | | | Pane - '' (L741, T937, R1593, B977)| | ['Pane8', ' 行 1,列 1Pane2']| | | | | | Static - ' 行 1,列 1' (L761, T947, R840, B967)| | | [' 行 1,列 1', ' 行 1,列 1Static', 'Static2']| | | child_window(title=" 行 1,列 1", auto_id="ContentTextBlock", control_type="Text")| | | | | | Static - '0 个字符' (L872, T947, R930, B967)| | | ['0 个字符', 'Static3', '0 个字符Static']| | | child_window(title="0 个字符", auto_id="ContentTextBlock", control_type="Text")| | | | | | Static - '纯文本' (L1024, T937, R1084, B977)| | | ['纯文本Static', 'Static4', '纯文本']| | | child_window(title="纯文本", auto_id="ContentTextBlock", control_type="Text")| | | | | | Static - ' 100%' (L1192, T947, R1233, B967)| | | [' 100%Static', ' 100%', 'Static5']| | | child_window(title=" 100%", auto_id="ContentTextBlock", control_type="Text")| | | | | | Static - ' Windows (CRLF)' (L1277, T947, R1388, B967)| | | [' Windows (CRLF)', ' Windows (CRLF)Static', 'Static6']| | | child_window(title=" Windows (CRLF)", auto_id="ContentTextBlock", control_type="Text")| | | | | | Static - ' UTF-8' (L1436, T947, R1480, B967)| | | [' UTF-8', 'Static7', ' UTF-8Static']| | | child_window(title=" UTF-8", auto_id="ContentTextBlock", control_type="Text")| | TitleBar - '' (L0, T0, R0, B0)| ['TitleBar']| | | | Menu - '系统' (L742, T140, R770, B168)| | ['Menu2', '系统Menu', '系统', '系统0', '系统1']| | child_window(title="系统", auto_id="MenuBar", control_type="MenuBar")| | | | | | MenuItem - '系统' (L742, T140, R770, B168)| | | ['MenuItem4', '系统MenuItem', '系统2']| | | child_window(title="系统", control_type="MenuItem")| | | | Button - '最小化' (L1415, T132, R1475, B169)| | ['最小化Button', 'Button10', '最小化']| | child_window(title="最小化", control_type="Button")| | | | Button - '最大化' (L1475, T132, R1534, B169)| | ['最大化', 'Button11', '最大化Button']| | child_window(title="最大化", control_type="Button")| | | | Button - '关闭' (L1534, T132, R1594, B169)| | ['关闭', '关闭Button', 'Button12']| | child_window(title="关闭", control_type="Button")
4.1 best_match来定位
best_match有两种定位方式:
main_window.文件(不能带空格,不推荐)
main_window["文件"]
import time
from pywinauto import Application# 连接到一个记事本
app = Application(backend="uia").connect(process=28304)
time.sleep(3)
# 获取主窗口
main_window = app.window(class_name="Notepad")
# 等待窗⼝变为可⻅状态,确保窗⼝已经加载完成
main_window.wait('visible')
# 通过窗⼝的标题获取菜单对象
menu=main_window["文件"]
# 打印父亲控件
print(menu.parent())
4.2 child_window()定位
child_window() 和定位窗⼝的⽅法 window() 参数⼀样,可以通过标题或者类名进行精确匹配、模糊匹配等,在这⾥额外拓展几个进⾏控件定位时需要用到的参数:
属性名称 | 描述 |
control_type | 具有此控件类型的元素(字符串;⽤于UIAutomation元素) |
auto_id | 具有此⾃动化ID的元素(⽤于UIAutomation元素) |
found_index | 返回的已过滤⼦元素的索引,索引下标从0开始 |
import time
from pywinauto import Application# 连接到一个记事本
app = Application(backend="uia").connect(process=28304)
time.sleep(3)
# 获取主窗口
main_window = app.window(class_name="Notepad")
# 等待窗⼝变为可⻅状态,确保窗⼝已经加载完成
main_window.wait('visible')
# 通过控件的属性(标题、⾃动ID和控件类型)定位菜单栏对象
# 也可以通过这三个属性的其中一个,来定位,但是不能有重复
menu=main_window.child_window(title="编辑",auto_id="Edit", control_type="MenuItem")
# 打印父亲控件
print(menu.parent())
若存在多个控件的 auto_id 相同,可通过添加索引参数来定位:
注意,若元素值为空,或不是唯⼀的情况下,都会导致定位失败,索引从0开始
5. 等待
GUI应用程序行为通常不稳定,脚本需要等待,直到出现新窗口或关闭/隐藏现有窗口。pywinauto可以隐式地(默认超时)灵活地等待对话框初始化,或者明确地使⽤专用方法/函数来帮助您使代码更容易和更可靠。
5.1 wait/wait_not()
wait(self, wait_for, timeout=None, retry_interval=None)
wait_not(self, wait_for_not, timeout=None, retry_interval=None)
参数说明:
1. wait_for :表⽰选择的窗口状态
exists :表⽰窗口是⼀个有效的句柄
visible :表⽰窗口不隐藏,可以看到
enable :表⽰窗口未被禁⽤,可操作
ready :表⽰窗口可⻅且已启⽤
active :表⽰窗口处于活动状态
2. timeout :表⽰超时
3. retry_interval :表⽰重试时间间隔,单位为秒s
注意:wait_not 与 wait() 类似, wait 是等待处于某种状态,而wait_not 是等待不处于某种状态,这⾥以wait为例说明
使用示例:exists和visible
import time
from pywinauto import Application# 连接到一个记事本
app = Application(backend="uia").connect(process=59164 )
# 获取主窗口
main_window = app.window(class_name="Notepad")#检查窗⼝是有效的句柄
main_window.wait('exists')
#检查窗⼝是否可⻅
main_window.wait('visible')
#检查窗⼝是否未被禁⽤
main_window.wait('enabled')
#检查窗⼝是否准备就绪
main_window.wait('ready')
窗口被最小化之后,在桌面就不可见了,因此最小化时需要将等待状态改为 "exists" ,应用程序界面在桌面可见时等待状态可以设置为 "visible"
enabled:表示窗口未被禁用,可操作
import time
from pywinauto import Application# 打开一个计算器
app = Application(backend="uia").connect(process=1136)
# 获取主窗口
main_window = app.window(title="计算器")
main_window.wait("visible",timeout=10)#启⽤的按钮
enable_btn = main_window.child_window(title="记忆减法", auto_id="MemMinus", control_type="Button")
#未启⽤的按钮
disabled_btn = main_window.child_window(title="清除所有记忆", auto_id="ClearMemoryButton", control_type="Button")
enable_btn.wait("enabled") #---代码执⾏通过
disabled_btn.wait_not("enabled")#---代码执⾏通过
若按钮置灰状态,说明按钮为 disabled 状态,当输入项不为空时,按钮高亮,此时为enabled 状态
ready:表示窗口可见且已启用
import time
from pywinauto import Application# 打开一个计算器
app = Application(backend="uia").connect(process=1136)
# 获取主窗口
main_window = app.window(title="计算器")
main_window.wait("visible")proc = main_window.child_window(title="打开导航", auto_id="TogglePaneButton", control_type="Button")
proc.wait("ready")#等待成功
proc_chid = main_window.child_window(auto_id="PaneTitleTextBlock", control_type="Text")
proc_chid.wait("ready")#等待失败
is_visible() 用于检查元素是否可见,除此之外,is_enabled() 用于检查元素是否启用
active :表示窗口处于活动状态
需要注意, 'active' 状态指的是窗口是否处于活动状态,需要先操作应用程序使得焦点设置在该窗口上或者配合 set_focus 来使⽤:
from pywinauto import Application# 连接计算机
app = Application(backend="uia").connect(process=1136)
# 获取窗口
win = app.window(title="计算器")
# 获取焦点
win.set_focus()
# 输入9
win.child_window(title="九", auto_id="num9Button",control_type="Button").click_input()
win.wait("active") #等待成功
5.2 wait_until()
等待满足某个条件
wait_until(timeout, retry_interval,func,value=True,op=operator.eq,*args, **kwargs)
部分参数说明:
-
Timeout :超时时间
-
retry_interval :重试时间
-
func :执行的函数
-
value :比较的值
i=0
def work():global ii += 1print("当前i的值为",i)return i# 总共等待10秒,每个一秒执行word函数,等待work返回的结果为5,否则继续往下执⾏
wait_until(10,1,work,5)
print("等待通过")
6. 控件的操作
6.1 点击
click_input():模拟鼠标左键单击操作
from pywinauto import Application# 连接记事本
app = Application(backend='uia').connect(process=71696)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")
#点击文件按钮
win.child_window(title="文件", auto_id="File", control_type="MenuItem").click_input()
right_click_input():模拟鼠标右键单击操作
from pywinauto import Application# 连接记事本
app = Application(backend='uia').connect(process=71696)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")
win.print_control_identifiers()
# 右击标签页
win.child_window(auto_id="Tabs", control_type="Tab").right_click_input()
double_click_input() :模拟鼠标右键双击操作
from pywinauto import Application# 连接计算器
app = Application(backend='uia').connect(process=1136)
# 获取窗口
win = app.window(title="计算器")
win.wait("exists")#双击按钮8
win.child_window(title="八", auto_id="num8Button",control_type="Button").double_click_input()
6.2 文本
texts():用于获取窗口或控件中的所有⽂本内容,返回⼀个列表,其中每个元素是⼀个字符串,表⽰窗⼝或控 件中的某个⽂本⽚段
获取窗口或控件的标题
from pywinauto import Application# 连接记事本
app = Application(backend='uia').connect(process=29680)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")
# 获取控件
control_title=win.child_window(title="编辑", auto_id="Edit", control_type="MenuItem")
control_title.wait("exists")
# 打印窗口标题
print(win.texts())
# 打印控件标题
print(control_title.texts())
window_text():⽤于获取窗⼝或控件的主⽂本内容,通常是指窗⼝的标题或主要显⽰的⽂本。它返回⼀个字符串。
7. 鼠标操作
在使⽤pywinauto进⾏⾃动化测试时,我们通常会利⽤控件的点击⽅法来实现交互操作。这些⽅法不仅⽀持对控件的直接点击,还允许通过指定coords 参数来实现基于坐标的点击操作。
例如,当我们尝试通过win.double_click_input(coords=(1043, 130)) 来点击SublimeText窗⼝的最⼤化按钮时,虽然语法上没有问题,但这种⽅法可能⽆法完全满⾜我们的需求,尤其是在需要更精确或更灵活的⿏标交互时。
from pywinauto import Application# 连接记事本
app = Application(backend='uia').connect(process=29680)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")# 点击记事本最大化按钮
win.double_click_input(coords=(763,16))
为了解决这⼀问题,pywinauto提供了⼀个独⽴的mouse 模块,专⻔⽤于模拟真实⽤⼾的⿏标事 件。这个模块的优势在于,它完全独⽴于控件操作,能够更贴近真实⽤⼾的⾏为模式。通过mouse 模块,我们可以直接在屏幕上指定坐标进行点击、双击、拖动等操作,⽽⽆需依赖控件的层次结构。
mouse模块常见操作:
⽅法 | 功能描述 |
mouse.click(button='left', coords=(0, 0)) | 单击指定的坐标 |
mouse.scroll(coords=(0, 0), wheel_dist=1) | 滚动⿏标滚轮,wheel_dist表⽰滚动的距离,⼤于0是向上滚动,⼩于0是向下 滑动。 |
mouse.double_click(button='left', coords=(0, 0)) | 双击指定的坐标 |
mouse.right_click(coords=(0, 0)) | 右键单击指定的坐标 |
mouse.move(coords=(0, 0)) | 移动鼠标到指定坐标 |
mouse.wheel_click(coords=(0, 0)) | 鼠标中键单击指定的坐标 |
mouse.press(button='left', coords=(0, 0)) | 按下鼠标按钮 |
mouse.release(button='left', coords=(0, 0)) | 释放鼠标按钮 |
rectangle() :获取元素对应坐标,返回矩形尺⼨,具有top,left,right,bottom属性
mid_point() :获取元素中间位置坐标,返回类型为元组,元组中两个整数分别是X、Y轴的值
rectangle() 和mid_point()使用方式
from pywinauto import Application# 连接记事本
app = Application(backend='uia').connect(process=29680)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")
# 获取控件
control_title=win.child_window(title="编辑", auto_id="Edit", control_type="MenuItem")
control_title.wait("exists")
# 获取控件的top,left,right,bottom
position=control_title.rectangle()
# 可以通过这种方式,将其打印出来
print("top="f'{position.top}',"left="f'{position.left}',"right="f'{position.right}',"bottom="f'{position.bottom}')
# 获取该控件或窗口的中间坐标
mid=win.rectangle().mid_point()
# 打印窗口中间的坐标
print("x="f"{mid.x}","y="f"{mid.y}")
mouse模块的使用
from pywinauto import Application, mouse# 连接记事本
app = Application(backend='uia').connect(process=29680)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")
# 获取控件
control_title=win.child_window(title="编辑", auto_id="Edit", control_type="MenuItem")
control_title.wait("exists")# 获取该控件或窗口的中间坐标
mid=control_title.rectangle().mid_point()
# 点击该控件
mouse.click(coords=(mid.x,mid.y))
time.sleep(3)
# 将鼠标移至可以通过鼠标滑动的位置
mouse.scroll(coords=(500,500),wheel_dist=-20)
8. 键盘操作
在⾃动化测试中,pywinauto提供了强⼤的键盘操作功能,其中keyboard模块是核⼼组件之⼀。 keyboard.send_keys() 是⼀个通⽤的键盘输⼊⽅法,它可以直接将按键序列发送到当前具有焦点的窗⼝。然⽽,在实际的⾃动化场景中,我们通常需要针对特定的控件(如⽂本框、按钮等)进⾏精确的输⼊操作,⽽不是依赖于窗⼝的焦点状态。
from pywinauto.keyboard import send_keys
send_keys("1234567")
为了满⾜这种需求,pywinauto对键盘输⼊功能进⾏了进⼀步的封装,引⼊了type_keys ⽅法。 type_keys 是控件对象(例如Edit控件或Button控件)的专属⽅法,可以直接作⽤于指定的控件,确保输⼊内容精准⽆误。这种⽅法避免了因焦点切换导致的输⼊错误,特别适合在复杂的⽤⼾界⾯中进⾏⾃动化操作
语法:
type_keys(keys, # 要输⼊的键序列,可以是普通字符、特殊键或组合键 pause = None, # 每次按键后的延迟时间(秒) with_spaces = False, # 如果为 True,则会在输⼊的字符串中保留空格。 with_newlines = False, # 如果为 True,则会在输⼊的字符串中保留换⾏符。
)
向当前焦点窗⼝⾃动键⼊键或发送⽂本。
8.1 输入文本
-
直接输⼊⽂本(⽀持Unicode字符):
from pywinauto import Application, mouse# 连接记事本
app = Application(backend='uia').connect(process=75580)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")#输⼊⽂本内容
win.type_keys("------type_keys------")
#保留换⾏符
win.type_keys("------type_keys------\n",with_newlines=True)
#保留空格
win.type_keys(" -----type keys----\n",with_spaces=True,with_newlines=True)
#延迟输⼊,避免输⼊过快导致内容不完整
win.type_keys("⼀⼆三四五六七",with_spaces=True)
8.2 按键输入
-
使⽤{VK_CODE} 格式表⽰虚拟键码,例如:
按键 | 代码 |
Enter | {ENTER} |
Tab | {TAB} |
Backspace | {BACKSPACE} |
Esc | {ESC} |
⽅向键 | {UP} ,{DOWN} ,{LEFT} ,{RIGHT} |
F1-F9 | {F1} - {F9} |
Shift | + |
Ctrl | ^ |
Alt | % |
例如:
import time
from pywinauto import Application, mouse# 连接记事本
app = Application(backend='uia').connect(process=75580)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")#发送⽂本和回⻋
win.type_keys("Hello World{ENTER}",with_spaces=True)
# +a==Shift+a==A
win.type_keys("+a你好+a,+a大帅哥+a{ENTER}",with_spaces=True)
# 全选和复制
win.type_keys("^a^c",with_spaces=True)
# 在中间位置点一下,重置光标的位置
mid=win.rectangle().mid_point()
mouse.click(coords=(mid.x,mid.y))
# 粘贴
win.type_keys("^v",with_spaces=True)
指定重复次数和转义特殊字符
-
可以为特殊键指定重复计数。 {ENTER 2} 表⽰按两次Enter键
-
使⽤{} 包裹特殊字符(如{+} ,{%} ,{^} )以避免被识别为修饰符
from pywinauto import Application, mouse# 连接记事本
app = Application(backend='uia').connect(process=75580)
# 获取窗口
win = app.window(class_name="Notepad")
win.wait("exists")# 指定重复次数
win.type_keys("Hello World{ENTER 5}",with_spaces=True)
win.type_keys("+a你好+a,+a大帅哥+a{ENTER}",with_spaces=True)
# 转义特殊字符
# win.type_keys("1+2=3") # 错误:`+` 会被识别为 Shift
win.type_keys("1{+}2=3") # 正确
9. 菜单控件的操作
items():返回对话框的菜单项,如果没有菜单项,则返回空列表
item_by_index(idx):查找索引指定的菜单项,idx :索引,从0开始
from pywinauto import Application# 连接VS code2022
app = Application(backend='uia').connect(process=18976)
# 获取窗口
win = app.window(class_name="Window")
win.wait("exists")# 定位菜单栏
menu=win.child_window(title="菜单栏", auto_id="MenuBar", control_type="MenuBar")
# 返回并打印菜单项
print(menu.items())
# 查找索引指定的菜单项,从0开始
print(menu.item_by_index(0))
print(menu.item_by_index(1))
item_by_path()
item_by_path(path, exact=False):⽤户查找路径指定的菜单项
path :⽤于指定要选择的菜单项路径。
exact :设置为True,则要求菜单项名称与路径中的名称完全匹配;如果为False,则允许模糊匹配路径可以是“MenuItem->MenuItem->MenuItem...”形式的字符串,其中每个MenuItem是菜单该 级别的项⽬⽂本。
from pywinauto import Application# 连接VS code2022
app = Application(backend='uia').connect(process=18976)
# 获取窗口
win = app.window(class_name="Window")
win.wait("exists")# 定位菜单栏
menu=win.child_window(title="菜单栏", auto_id="MenuBar", control_type="MenuBar")# # 方法一 通过item_by_path
# # menu.item_by_path("文件(F)->新建(N)->项目(P)...").click_input()
menu.item_by_path("文件(F)->main.cpp另存为(A)...").click_input()
# 方法二 通过按键
file=menu. child_window(title="文件", control_type="MenuItem").click_input()
win.type_keys("A")
空格并不重要,所以你也可以写...
File -> Export -> Export As PNG
menu_select()
menu_select(path, exact=False):⽤户查找路径指定的菜单项
path :⽤于指定要选择的菜单项路径。
exact :设置为True,则要求菜单项名称与路径中的名称完全匹配;如果为False,则允许模糊匹配 注意, menu_select() 的使⽤和上⾯有区别,使⽤ menu_select() 的场景下,通常⾄少有两个 菜单栏:“系统”和“应⽤程序”系统菜单栏是⼀个标准的窗⼝菜单,包含以下项⽬:“还 原”、“移动”、“⼤⼩”、“最⼩化”等。此菜单栏通常有⼀个“标题栏”控件作为⽗级。应⽤程序菜单栏通常是我们要找的。在⼤多数情况下,它的⽗级是对话框本⾝,因此可以在对话框的直接⼦ 级中找到它。
10. 列表控件的操作
get_items():获取列表视图中的所有项⽬。
item_count():列表视图中的项数。
get_item():返回列表视图中的指定项⽬。row :可以是⾏的索引
⽰例:打印⽂件资源管理列表项
from pywinauto import Application# 连接文件资源管理器
app = Application(backend='uia').connect(process=9904)
# 获取窗口
win = app.window(class_name="CabinetWClass")
win.wait("visible")
# 获取list表的定位,最近访问
list=win.child_window(auto_id="HomeListView", control_type="List")
# 打印最近访问列表
print("items:",list.get_items())
#打印列表项数量
print("items:",list.item_count())
#获取列表中第⼀项
print("items:",list.get_item(row=0))
五、自动化测试场景示例
给抖音自动点赞
# 连接抖音
app = Application(backend='uia').connect(process=10072)
# 获取窗口
win = app.window(title="抖音")
win.wait("exists")
# win.print_control_identifiers()for i in range (0,3):# 定位窗口中间位置mid=win.rectangle().mid_point()# 双击该位置mouse.double_click(button='left', coords=(mid.x, mid.y))time.sleep(1)# 滑动该位置mouse.scroll(coords=(mid.x, mid.y), wheel_dist=-500)