1.简介
前边的几篇文章,宏哥依次介绍了环境的搭建、启动应用程序、操作应用程序窗口以及等待的各种方法和实践。今天跟随宏哥一起来看下应用程序窗口的控件怎么操作呢???其实这个知识点前边已经有所涉及,只不过是一带而过,没有展开详细介绍。今天就来讲解和分享一下。PC端的应用程序的操作都是基于控件操作,如果要操作控件,我们就必须首先要找到控件对应的窗口,因此我们只需要基于控件查找对应的窗口框架,然后定位到控件即可操作了。那么跟随宏哥先来看一下常用的定位控件的方法。
2.常用定位控件方法
2.1通过层级查找控件相关方法
常用的定位控件的方法如下:
- window(**kwargs) # 用于窗口的查找
- child_window(**kwargs) # 可以无视层级的找后代中某个符合条件的元素===>【最常用】
- parent() # 返回此元素的父元素,没有参数
- children(**kwargs) # 返回符合条件的子元素列表,支持索引,是BaseWrapper对象(或子类)
- iter_children(**kwargs) # 返回子元素的迭代器,是BaseWrapper对象(或子类)
- descendants(**kwargs) # 返回符合条件的所有后代元素列表,是BaseWrapper对象(或子类)
- iter_children(**kwargs) # 符合条件后代元素迭代器,是BaseWrapper对象(或子类)
2.2**kwargs 常用的一些参数
**kwargs 常用的一些参数如下:
class_name=None, # 类名 class_name_re=None, # 正则匹配类名 title=None, # 控件的标题文字,对应inspect中Name字段 title_re=None, # 正则匹配文字 control_type=None, # 控件类型,inspect界面LocalizedControlType字段的英文名 best_match=None, # 模糊匹配类似的title auto_id=None, # inspect界面AutomationId字段,但是很多控件没有这个属性 parent=None, process=None, # 这个基本不用,每次启动进程都会变化 top_level_only=True, visible_only=True, enabled_only=False, handle=None, ctrl_index=None, found_index=None, predicate_func=None, active_only=False, control_id=None, framework_id=None, backend=None,
3.查看窗口控件
要想操作窗口控件吗,我们需要先学会如何查看窗口框架,查看窗口的结构树。宏哥这里介绍两种比较常用的方法,而且这两种方法前边都有所介绍和讲解,这里简单演示一下即可。
3.1方法一(工具)
可以用inspect.exe 查看窗口的层级结构树。
1.双击启动inspect.exe软件,然后点击窗口的箭头按钮,如下图所示:
2.然后鼠标再次点击你要查看的窗口(宏哥要查看记事本的编辑区域的窗口结构树,点击箭头,然后再点击记事本的编辑区域)。如下图所示:
3.2方法二(代码)
可以通过print_ctrl_ids()
方法 (另外一个print_control_identifiers()
功能一样,这个前边的文章已经介绍过也用过,今天介绍没有用过的)查看当前窗口下的控件。
3.2.1代码设计
3.2.2参考代码
# -*- coding:utf-8 -*- # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行 # 2.注释:包括记录创建时间,创建人,项目名称。 ''' Created on 2025-04-18 @author: 北京-宏哥 北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!) Project: PC端自动化测试实战教程-8-pywinauto操作应用程序窗口的控件(详细教程) ''' # 3.导入模块 from pywinauto import Application import time # 通过窗口打开 app = Application('uia').start("notepad.exe") time.sleep(10) app = Application('uia').connect(class_name="Notepad",visible_only=False) win = app['无标题 - Notepad'] win.print_ctrl_ids()
3.2.3运行代码
1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:
2.运行代码后电脑端的动作(启动记事本,打印窗口结构树)。如下图所示:
4.对比工具和代码查看的控件
因为要在记事本的编辑区域输入内容,所以我们需要操作编辑区域窗口的控件。但是此时宏哥发现工具查看的窗口结构树和代码打印的完全不一致,这难道因为宏哥的是Windows11系统吗???
1.工具查看记事本编辑区域,如下图所示:
2.代码打印记事本结构树,如下图所示:
D:\Python\python.exe D:/Demo/test.py Control Identifiers: Dialog - '无标题 - Notepad' (L0, T0, R0, B0) ['无标题 - NotepadDialog', 'Dialog', '无标题 - Notepad', 'Dialog0', 'Dialog1'] child_window(title="无标题 - Notepad", control_type="Window") | | Pane - '' (L-31992, T-31907, R-31297, B-31554) | ['无标题Pane', 'Pane', '无标题Pane0', '无标题Pane1', 'Pane0', 'Pane1'] | | | | Document - '' (L-31992, T-31907, R-31297, B-31554) | | ['无标题Document', 'Document'] | | Pane - '' (L-31942, T-31988, R-31655, B-31956) | ['无标题Pane2', 'Pane2'] | | | | TabControl - '' (L-31942, T-31988, R-31583, B-31948) | | ['TabControl添加新标签页', 'TabControl', '无标题TabControl'] | | child_window(auto_id="Tabs", control_type="Tab") | | | | | | ListBox - '' (L-31940, T-31988, R-31628, B-31948) | | | ['无标题ListBox', 'ListBox'] | | | child_window(auto_id="TabListView", control_type="List") | | | | | | | | TabItem - '无标题. 未修改。' (L-31937, T-31988, R-31630, B-31948) | | | | ['无标题. 未修改。', '无标题. 未修改。TabItem', 'TabItem'] | | | | child_window(title="无标题. 未修改。", control_type="TabItem") | | | | | | | | | | Static - '无标题' (L-31921, T-31978, R-31876, B-31959) | | | | | ['Static', '无标题', '无标题Static', 'Static0', 'Static1'] | | | | | child_window(title="无标题", control_type="Text") | | | | | | | | | | Button - '关闭标签页' (L-31681, T-31983, R-31641, B-31953) | | | | | ['Button', '关闭标签页', '关闭标签页Button', 'Button0', 'Button1'] | | | | | child_window(title="关闭标签页", auto_id="CloseButton", control_type="Button") | | | | | | Button - '添加新标签页' (L-31623, T-31982, R-31583, B-31952) | | | ['添加新标签页', 'Button2', '添加新标签页Button'] | | | child_window(title="添加新标签页", auto_id="AddButton", control_type="Button") | | | | Pane - '记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。' (L0, T0, R0, B0) | | ['记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。Pane', '记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。', 'Pane3'] | | child_window(title="记事本自动保存进度。下次打开记事本时,你的所有内容都将可用。", auto_id="TeachingTip", control_type="Pane") | | Pane - '' (L-31992, T-31948, R-31436, B-31916) | ['无标题Pane3', 'Pane4'] | | | | Menu - '' (L-31992, T-31948, R-31782, B-31908) | | ['Menu', '无标题Menu', 'Menu0', 'Menu1'] | | child_window(auto_id="MenuBar", control_type="MenuBar") | | | | | | MenuItem - '文件' (L-31987, T-31948, R-31927, B-31908) | | | ['MenuItem', '文件MenuItem', '文件', 'MenuItem0', 'MenuItem1'] | | | child_window(title="文件", auto_id="File", control_type="MenuItem") | | | | | | MenuItem - '编辑' (L-31917, T-31948, R-31857, B-31908) | | | ['编辑', 'MenuItem2', '编辑MenuItem'] | | | child_window(title="编辑", auto_id="Edit", control_type="MenuItem") | | | | | | MenuItem - '查看' (L-31847, T-31948, R-31787, B-31908) | | | ['MenuItem3', '查看', '查看MenuItem'] | | | child_window(title="查看", auto_id="View", control_type="MenuItem") | | | | Button - '设置' (L-31345, T-31948, R-31307, B-31908) | | ['设置Button', 'Button3', '设置'] | | child_window(title="设置", auto_id="SettingsButton", control_type="Button") | | | | Dialog - '' (L0, T0, R0, B0) | | ['Dialog2'] | | child_window(auto_id="CowriterTeachingTip", control_type="Window") | | Pane - '' (L-31992, T-31554, R-31436, B-31522) | [' 行 1,列 1Pane', 'Pane5'] | | | | Static - ' 行 1,列 1' (L-31972, T-31544, R-31893, B-31524) | | ['Static2', ' 行 1,列 1', ' 行 1,列 1Static'] | | child_window(title=" 行 1,列 1", auto_id="ContentTextBlock", control_type="Text") | | | | Static - '0 个字符' (L-31861, T-31544, R-31803, B-31524) | | ['Static3', '0 个字符', '0 个字符Static'] | | child_window(title="0 个字符", auto_id="ContentTextBlock", control_type="Text") | | | | Static - ' 100%' (L-31700, T-31544, R-31659, B-31524) | | ['Static4', ' 100%', ' 100%Static'] | | child_window(title=" 100%", auto_id="ContentTextBlock", control_type="Text") | | | | Static - ' Windows (CRLF)' (L-31616, T-31544, R-31505, B-31524) | | ['Static5', ' Windows (CRLF)Static', ' Windows (CRLF)'] | | child_window(title=" Windows (CRLF)", auto_id="ContentTextBlock", control_type="Text") | | | | Static - ' UTF-8' (L-31456, T-31544, R-31412, B-31524) | | ['Static6', ' UTF-8Static', ' UTF-8'] | | child_window(title=" UTF-8", auto_id="ContentTextBlock", control_type="Text") | | TitleBar - '' (L0, T0, R0, B0) | [' UTF-8TitleBar', 'TitleBar'] | | | | Menu - '系统' (L-31991, T-31991, R-31963, B-31963) | | ['系统', 'Menu2', '系统Menu', '系统0', '系统1'] | | child_window(title="系统", auto_id="MenuBar", control_type="MenuBar") | | | | | | MenuItem - '系统' (L-31991, T-31991, R-31963, B-31963) | | | ['系统2', 'MenuItem4', '系统MenuItem'] | | | child_window(title="系统", control_type="MenuItem") | | | | Button - '还原' (L-31987, T-31999, R-31927, B-31973) | | ['Button4', '还原Button', '还原'] | | child_window(title="还原", control_type="Button") | | | | Button - '最大化' (L-31927, T-31999, R-31868, B-31973) | | ['Button5', '最大化', '最大化Button'] | | child_window(title="最大化", control_type="Button") | | | | Button - '关闭' (L-31868, T-31999, R-31808, B-31973) | | ['关闭', 'Button6', '关闭Button'] | | child_window(title="关闭", control_type="Button") Process finished with exit code 0
宏哥看了代码打印的根本没有工具查看的“文本编辑器”和Classname是“RichEditD2DPT”。这怎么搞......查了好几天资料也没有查到有用的或者能解决问题的方法。然后就打算放弃了...最后突然想到要不把那个“uia”换成“win32”试一下,但是总感觉不靠谱,因为工具查看的记事本是uia。不管了先试一下吧!
4.1参考代码
# -*- coding:utf-8 -*- # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行 # 2.注释:包括记录创建时间,创建人,项目名称。 ''' Created on 2025-04-18 @author: 北京-宏哥 北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!) Project: PC端自动化测试实战教程-8-pywinauto操作应用程序窗口的控件(详细教程) ''' # 3.导入模块 from pywinauto import Application import time # 通过窗口打开 app = Application('win32').start("notepad.exe") time.sleep(10) app = Application('win32').connect(class_name="Notepad",visible_only=False) win = app['无标题 - Notepad'] win.print_ctrl_ids()
4.2运行代码
1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:
2.出乎意料啊,从上图的控制台,竟然看到了工具查看的classname。不管三七二十一,宏哥觉得离胜利更进一步了。宏哥接着就要尝试能不能操作这个记事本编辑窗口的控件然后输入内容,如果可以就大功告成了。打印结构树如下:
D:\Python\python.exe D:/Demo/test.py D:\Python\lib\site-packages\pywinauto\application.py:1076: RuntimeWarning: Application is not loaded correctly (WaitForInputIdle failed) warnings.warn('Application is not loaded correctly (WaitForInputIdle failed)', RuntimeWarning) Control Identifiers: Notepad - '无标题 - Notepad' (L1011, T190, R1722, B684) ['Notepad', '无标题 - Notepad', '无标题 - NotepadNotepad'] child_window(title="无标题 - Notepad", class_name="Notepad") | | Windows.UI.Composition.DesktopWindowContentBridge - 'DesktopWindowXamlSource' (L1019, T615, R1696, B633) | ['Windows.UI.Composition.DesktopWindowContentBridge', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge', 'DesktopWindowXamlSource', 'Windows.UI.Composition.DesktopWindowContentBridge0', 'Windows.UI.Composition.DesktopWindowContentBridge1', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge0', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge1', 'DesktopWindowXamlSource0', 'DesktopWindowXamlSource1'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Composition.DesktopWindowContentBridge") | | | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T615, R1019, B615) | | ['Windows.UI.Input.InputSite.WindowClass', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass', 'Windows.UI.Input.InputSite.WindowClass0', 'Windows.UI.Input.InputSite.WindowClass1', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass0', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass1'] | | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T615, R1019, B615) | ['Windows.UI.Input.InputSite.WindowClass', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass', 'Windows.UI.Input.InputSite.WindowClass0', 'Windows.UI.Input.InputSite.WindowClass1', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass0', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass1'] | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Composition.DesktopWindowContentBridge - 'DesktopWindowXamlSource' (L1693, T283, R1711, B636) | ['Windows.UI.Composition.DesktopWindowContentBridge2', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge2', 'DesktopWindowXamlSource2'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Composition.DesktopWindowContentBridge") | | | | Windows.UI.Input.InputSite.WindowClass - '' (L1693, T283, R1693, B283) | | ['Windows.UI.Input.InputSite.WindowClass2', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass2'] | | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Input.InputSite.WindowClass - '' (L1693, T283, R1693, B283) | ['Windows.UI.Input.InputSite.WindowClass2', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass2'] | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | NotepadTextBox - '' (L1019, T283, R1714, B636) | ['无标题 - NotepadNotepadTextBox', 'NotepadTextBox'] | child_window(class_name="NotepadTextBox") | | | | Edit - '' (L1019, T283, R1714, B636) | | ['Edit', '无标题 - NotepadEdit'] | | child_window(class_name="RichEditD2DPT") | | Edit - '' (L1019, T283, R1714, B636) | ['Edit', '无标题 - NotepadEdit'] | child_window(class_name="RichEditD2DPT") | | Windows.UI.Core.CoreWindow - 'DesktopWindowXamlSource' (L1019, T190, R1020, B191) | ['DesktopWindowXamlSourceWindows.UI.Core.CoreWindow', 'Windows.UI.Core.CoreWindow', 'DesktopWindowXamlSource3'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Core.CoreWindow") | | Windows.UI.Composition.DesktopWindowContentBridge - 'DesktopWindowXamlSource' (L1069, T202, R1428, B242) | ['Windows.UI.Composition.DesktopWindowContentBridge3', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge3', 'DesktopWindowXamlSource4'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Composition.DesktopWindowContentBridge") | | | | Windows.UI.Input.InputSite.WindowClass - '' (L1069, T202, R1069, B202) | | ['Windows.UI.Input.InputSite.WindowClass3', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass3'] | | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Input.InputSite.WindowClass - '' (L1069, T202, R1069, B202) | ['Windows.UI.Input.InputSite.WindowClass3', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass3'] | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Composition.DesktopWindowContentBridge - 'DesktopWindowXamlSource' (L1019, T242, R1714, B283) | ['Windows.UI.Composition.DesktopWindowContentBridge4', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge4', 'DesktopWindowXamlSource5'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Composition.DesktopWindowContentBridge") | | | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T242, R1019, B242) | | ['Windows.UI.Input.InputSite.WindowClass4', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass4'] | | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T242, R1019, B242) | ['Windows.UI.Input.InputSite.WindowClass4', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass4'] | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Composition.DesktopWindowContentBridge - 'DesktopWindowXamlSource' (L1019, T636, R1714, B676) | ['Windows.UI.Composition.DesktopWindowContentBridge5', 'DesktopWindowXamlSourceWindows.UI.Composition.DesktopWindowContentBridge5', 'DesktopWindowXamlSource6'] | child_window(title="DesktopWindowXamlSource", class_name="Windows.UI.Composition.DesktopWindowContentBridge") | | | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T636, R1019, B636) | | ['Windows.UI.Input.InputSite.WindowClass5', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass5'] | | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") | | Windows.UI.Input.InputSite.WindowClass - '' (L1019, T636, R1019, B636) | ['Windows.UI.Input.InputSite.WindowClass5', '无标题 - NotepadWindows.UI.Input.InputSite.WindowClass5'] | child_window(class_name="Windows.UI.Input.InputSite.WindowClass") Process finished with exit code 0
5.记事本编辑区域输入内容
上边已经大致解决了代码和工具查看窗口控件结构树不一致的情况,接下来要趁热打铁看看能不能输入内容。
5.1测试场景
测试场景:大致的测试场景就是,启动电脑的记事本这款软件,然后操作记事本编辑区域的窗口控件,输入内容:北京宏哥,
5.2代码设计
5.3参考代码
# -*- coding:utf-8 -*- # 1.先设置编码,utf-8可支持中英文,如上,一般放在第一行 # 2.注释:包括记录创建时间,创建人,项目名称。 ''' Created on 2025-04-18 @author: 北京-宏哥 北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!) Project: PC端自动化测试实战教程-8-pywinauto操作应用程序窗口的控件(详细教程) ''' # 3.导入模块 from pywinauto import Application import time # 通过窗口打开 app = Application('win32').start("notepad.exe") time.sleep(10) app = Application('win32').connect(class_name="Notepad",visible_only=False) win = app['无标题 - Notepad'] # win.print_ctrl_ids() # 输入内容 win.child_window(class_name="RichEditD2DPT").type_keys("北京-宏哥")
5.4运行代码
1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:
2.运行代码后电脑端的动作(成功启动记事本,然后操作记事本的编辑区域窗口控件,最后在记事本的编辑区域输入内容:北京-宏哥)。如下图所示:
5.5其他方法
宏哥都试过了,可以成功输入内容。
中括号定位控件输入内容的其他方法如下:
win["Edit"].type_keys("北京-宏哥") win["Edit"].set_text("北京-宏哥")
点窗口定位控件输入内容的其他方法如下:
win.Edit.type_keys("北京-宏哥") win.Edit.set_text("北京-宏哥")
6.小结
以下是pywinauto操作应用程序窗口控件的核心方法总结:
6.1控件定位方法
6.1.1基础定位
通过窗口标题或类名获取窗口对象:
win = app.window(title="窗口标题", class_name="窗口类名") # :ml-citation{ref="2,5" data="citationList"}
使用child_window()定位子控件,支持参数包括title、auto_id、control_type等:
edit_box = win.child_window(title="文本编辑器", control_type="Edit") # :ml-citation{ref="3,6" data="citationList"}
6.1.2层级遍历
通过print_control_identifiers()或print_ctrl_ids()打印窗口层级结构,快速查看控件属性及层级关系 。
结合descendants()或children()方法遍历子控件:
all_buttons = win.descendants(control_type="Button") # :ml-citation{ref="6,8" data="citationList"}
6.2常用控件操作
控件类型 操作方法示例 说明
按钮 button.click_input() 模拟鼠标点击
编辑框 edit.set_text("内容") 输入文本
复选框 checkbox.check() 或 checkbox.uncheck() 勾选/取消勾选
下拉框 combo.select("选项名") 选择指定选项
列表项 list_view.select("项名") 选中指定项
菜单 menu.item("文件").click() 点击级联菜单项
6.3复杂控件处理
6.3.1动态控件
使用wait()方法等待控件出现,避免操作超时:
popup = win.child_window(title="提示").wait("visible", timeout=10) # :ml-citation{ref="3,6" data="citationList"}
6.3.2嵌套控件
通过多级child_window()逐层定位:
sub_control = win.child_window(title="父控件").child_window(auto_id="子控件ID") # :ml-citation{ref="3,6" data="citationList"}
6.4工具辅助
Inspect工具:查看窗口控件的title、class_name、control_type等属性,辅助编写定位逻辑 。
Spy++:获取窗口句柄及层级结构,验证控件属性 。
6.5注意事项
优先使用control_type和auto_id定位控件,避免因标题动态变化导致脚本失效 。
对现代应用(如WPF/UWP)使用backend="uia",传统Win32应用可选用backend="win32" 。
结合send_keys()模拟键盘操作(如快捷键Ctrl+S),增强脚本兼容性 。
通过以上方法可覆盖大多数Windows应用程序的控件自动化操作场景。
好了,时间不早了今天就分享到这里,感谢你耐心地阅读!