前言
- 在软件开发领域,自动化测试是一项重要的实践,它可以提高测试效率、减少人力成本,并确保软件质量。PC 自动化测试特指针对 Windows 平台的应用程序进行自动化测试,而 pywinauto 是一款用于实现 Windows GUI 应用程序自动化测试的 Python 库。本文将介绍 PC 自动化测试的基本概念、常用工具以及初识 pywinauto,以及通过一个操作记事本的示例演示其基本用法。
PC 自动化测试 是什么?
- PC 自动化测试是指利用自动化工具或脚本来模拟用户操作,对 Windows 平台的应用程序进行功能测试、性能测试等,以验证其是否符合预期行为。相比手动测试,自动化测试可以提高测试效率、减少测试成本,并且可以在持续集成和持续交付流程中实现自动化测试。
常用 PC 自动化测试工具
- 在 PC 自动化测试领域,有许多常用的工具,如:
txt
- Selenium:用于 Web 应用程序的自动化测试。
- pywinauto:用于 Windows GUI 应用程序的自动化测试。
- AutoIt:用于 Windows 平台的自动化测试,支持模拟键盘和鼠标操作。
pywinauto 是什么?
- pywinauto 是一款基于 Python 的开源库,用于自动化测试 Windows 平台的 GUI 应用程序。它能够模拟用户的键盘和鼠标操作,以及获取和修改应用程序的控件属性。pywinauto 提供了简单而强大的 API,使得开发人员可以轻松地编写自动化测试脚本。
Windows上支持的可访问性技术列表
- 一旦你安装了pywinauto,第一件必要的事情是确定您的应用程序可以使用哪种可访问性技术(pywinauto的后端)。常用的有 Win32 API、MS UI。
- 如果你不知道程序到底适用于那种可访问技术,可以借助于GUI对象检查工具来做,常用的检查工具有Inspect.exe,Spy++ 等,下面以 Inspect.exe 为例:
操作记事本自动写入
- 环境:win 10、Python 3.12
Python
from pywinauto import Application
# 连接 PC 应用的两种方式
# app = Application(backend="uia").start("notepad.exe")
app = Application(backend="uia").connect(process=32120)
print(app.process)
# 获取主窗口
top_window = app.window(title="无标题 - Notepad", control_type="Window")
# 打印控件菜单树结构
top_window.print_control_identifiers()
# 获取输入框
document = top_window.child_window(control_type="Document")
document.print_control_identifiers()
# 标出是否正确选中输入框
document.draw_outline(colour='red')
# 写入
document.click_input()
document.type_keys(keys="Your text here", with_spaces=True)
- 输出结果:
txt
32120
Control Identifiers:
Dialog - '无标题 - Notepad' (L-1512, T194, R-192, B886)
['Dialog', '无标题 - NotepadDialog', '无标题 - Notepad']
child_window(title="无标题 - Notepad", control_type="Window")
|
| Pane - '' (L-1505, T280, R-199, B879)
| ['Pane', '无标题Pane', 'Pane0', 'Pane1', '无标题Pane0', '无标题Pane1']
| |
| | Document - '' (L-1505, T280, R-199, B879)
| | ['Document', '无标题Document']
|
| Pane - '' (L-1459, T205, R-1173, B237)
| ['Pane2', '无标题Pane2']
| |
| | TabControl - '' (L-18704, T-17045, R-1129, B241)
| | ['TabControl', 'TabControl添加新标签页']
| | child_window(auto_id="Tabs", control_type="Tab")
| | |
| | | ListBox - '' (L-18704, T-17045, R-1169, B241)
| | | ['ListBox']
| | | child_window(auto_id="TabListView", control_type="List")
| | | |
| | | | TabItem - '无标题. 未修改。' (L-1455, T205, R-1173, B241)
| | | | ['无标题. 未修改。TabItem', '无标题. 未修改。', 'TabItem']
| | | | child_window(title="无标题. 未修改。", control_type="TabItem")
| | | | |
| | | | | Static - '无标题' (L-1440, T214, R-1398, B232)
| | | | | ['Static', '无标题', '无标题Static']
| | | | | child_window(title="无标题", control_type="Text")
| | | | |
| | | | | Button - '关闭标签页' (L-1219, T209, R-1183, B236)
| | | | | ['Button', '关闭标签页', '关闭标签页Button', 'Button0', 'Button1']
| | | | | child_window(title="关闭标签页", auto_id="CloseButton", control_type="Button")
| | |
| | | Button - '添加新标签页' (L-1166, T210, R-1130, B237)
| | | ['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-1505, T242, R-365, B275)
| ['Pane4', '无标题Pane3']
| |
| | Menu - '' (L-1505, T242, R-1310, B278)
| | ['Menu', '无标题Menu', 'Menu0', 'Menu1']
| | child_window(auto_id="MenuBar", control_type="MenuBar")
| | |
| | | MenuItem - '文件' (L-1500, T242, R-1444, B278)
| | | ['文件', '文件MenuItem', 'MenuItem', 'MenuItem0', 'MenuItem1']
| | | child_window(title="文件", auto_id="File", control_type="MenuItem")
| | |
| | | MenuItem - '编辑' (L-1435, T242, R-1380, B278)
| | | ['编辑', '编辑MenuItem', 'MenuItem2']
| | | child_window(title="编辑", auto_id="Edit", control_type="MenuItem")
| | |
| | | MenuItem - '查看' (L-1370, T242, R-1314, B278)
| | | ['查看', '查看MenuItem', 'MenuItem3']
| | | child_window(title="查看", auto_id="View", control_type="MenuItem")
| |
| | Button - '设置' (L-237, T243, R-203, B277)
| | ['Button3', '设置', '设置Button']
| | child_window(title="设置", auto_id="SettingsButton", control_type="Button")
|
| TitleBar - '' (L0, T0, R0, B0)
| ['TitleBar']
| |
| | Menu - '系统' (L-1503, T203, R-1478, B228)
| | ['Menu2', '系统', '系统Menu', '系统0', '系统1']
| | child_window(title="系统", auto_id="MenuBar", control_type="MenuBar")
| | |
| | | MenuItem - '系统' (L-1503, T203, R-1478, B228)
| | | ['MenuItem4', '系统MenuItem', '系统2']
| | | child_window(title="系统", control_type="MenuItem")
| |
| | Button - '最小化' (L-356, T195, R-303, B229)
| | ['Button4', '最小化Button', '最小化']
| | child_window(title="最小化", control_type="Button")
| |
| | Button - '最大化' (L-303, T195, R-251, B229)
| | ['Button5', '最大化', '最大化Button']
| | child_window(title="最大化", control_type="Button")
| |
| | Button - '关闭' (L-251, T195, R-198, B229)
| | ['Button6', '关闭', '关闭Button']
| | child_window(title="关闭", control_type="Button")
Control Identifiers:
Document - '' (L-1505, T280, R-199, B879)
['Document']
问题
app = Application(backend="uia").start("notepad.exe")
无法正常启动
Python
app = Application(backend="uia").start("notepad.exe")
print(app.process)
打印的进程ID为 2643,但实际进程ID为 836,导致无法查找到元素:
pywinauto.findwindows.ElementNotFoundError: {'title': '无标题 - Notepad', 'control_type': 'Window', 'backend': 'uia', 'process': 21300}
有知道朋友可以帮忙解答一下,十分感谢
组件选择器和 print_control_identifiers
打印的组件不匹配
- 使用了几个组件选择器:Inspect.exe、Spy++,感觉 Inspect.exe 最好用,但组件选择器和组件树不匹配,需要以打印的组件树为准。
- 比如上面 demo 中关于输入框组件:
- 组件树的打印结果:
txt
| | Document - '' (L-1505, T280, R-199, B879)
| | ['Document', '无标题Document']
- Inspect.exe 的结果:
- Inspect.exe 的结果中有 name 字段,但实际上组件并没有 name,使用 name 会导致组件无法匹配到。
多个相同组件定位问题
- 当使用 child_window 方法查找组件元素时,我们使用单一条件可能查到到多个组件,我们可以使用多个条件来尽可能确定唯一元素,比如下面这些条件:
参考
个人简介
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。
🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。
💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。
🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
📖 保持关注我的博客,让我们共同追求技术卓越。