PC 自动化测试入门 - pywinauto 上篇:初识

前言

  • 在软件开发领域,自动化测试是一项重要的实践,它可以提高测试效率、减少人力成本,并确保软件质量。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}

有知道朋友可以帮忙解答一下,十分感谢
  • 使用了几个组件选择器: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编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

相关推荐
程序员cxuan27 分钟前
速度太快了!本地可以跑 DeepSeek-V4-Flash 了
人工智能·后端·程序员
阿祖zu2 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才2 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒2 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20232 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei2 小时前
controller注册及调用时机
后端
edwarddamon3 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈3 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas1003 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
计算机魔术师4 小时前
传统基础架构 - 微服务
大数据·后端·微服务·架构·开发工具·编程语言