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编程。

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

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

相关推荐
Estar.Lee5 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang1 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
Rverdoser3 小时前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
Tech Synapse3 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811924 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
代码吐槽菌5 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm
豌豆花下猫6 小时前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai