GUI 自动化测试 pywinauto测试框架

自动化测试分类

GUI 自动化测试

GUI自动化的意义

  • 在软件测试过程中,有许多重复性的测试用例需要执行,例如对软件的各种基本功能进行验证。如果手动测试,测试人员需要不断地重复相同的操作步骤,这不仅耗时而且容易出错。而GUI 自动化测试工具可以在短时间内快速执行大量重复的测试用例,并且能够始终按照预设的逻辑和步骤进行操作,从而极大的提高了测试效率。
  • 通过GUI自动化测试,可以减少对人力的依赖。原本需要多名测试人员花费数小时甚至数天才能完成的测试工作,现在只需编写号自动化测试脚本,由自动化测试工具在规定时间内完成,这样可以将测试人员从繁琐的重复性工作中解放出来,在一定程度上降低了人力成本

GUI自动化使用场景

GUI 自动化测试适用场景:界面不会频繁发生改变,交互不会过于复杂

GUI 自动化适用于各种需要对图像用户界面进行重复性、一致性测试的场景,尤其在验证软件功能的稳定性、正确性和用户体验方面发挥着重要作用

在软将开发的各个阶段,从集成测试到验收测试,它都可以帮助测试人员快速发现潜在问题,如界面元素是否正常显示、交互是否流畅、功能是否按预期工作等,从而确保软件质量。

GUI 自动化可以模拟不同用户环境下的操作,测试其兼容性和稳定性。此外,在进行大规模测试时,它能显著提高测试效率,减少人力成本,快速提供反馈,支持持续集成和持续交付,加速软件开发周期。

总之,GUI 自动化使用于需要高效、准确、重复测试GUI 应用程序的各种场景,有助于提升软件质量、用户满意度和开发效率。需要注意的是,尽管GUI 自动化适用于上述场景,但其成功依赖于界面元素的稳定性------若UI 频繁变动或涉及复杂交互,维护成本可能徒增

Pywinauto

Pywinauto 是一款基于Python 的跨平台GUI 自动化库,专门针对Windows桌面应用程序设计,其核心能力在于通过模拟用户交互行为(如鼠标点击、键盘输入)实现对窗口、对话框及内部控件的精准定位与操作,适用于自动化测试、批量任务处理及日常办公流程优化等场景。

该库通过**两种底层技术(backend="win32" 和 backend="uia")**适配不同框架开发的应用程序:

  • win32模式适用于传统MFC、VB6等旧框架
  • uia模式则支持现代WinForms、WPF、Qt5及浏览器等应用

pywinauto 的优势

  • 基于Python:Python语言简洁易学,适合快速开发和维护
  • 跨平台支持:支持Windows 7及以上版本,兼容性良好
  • 丰富的控件支持:支持Windows 原生控件(如按钮、文本框、表格)及第三方控件(如WPF、Qt)
  • 动态查找机制:自动等待控件加载完成,无需显式等待
  • 强大的调试工具:提供 pywinauto.findwindows 模块,方便定位控件

pywinauto 的局限性

  • 仅支持Windows:无法用于MAC 和 Linux 平台
  • 对非标准控件支持有限:某些自定义控件可能需要额外处理

注意:pywinauto 支持传统 Windows 原生应用框架和部分跨平台框架 (需在 Windows 运行),以下是一些无法使用 pywinauto 实现GUI 自动化的应用程序分类,以及原因:

  • 基于 Web 的应用程序
    原因:pywinauto 是为本地Windows 应用程序设计的,无法直接与基于浏览器的Web 应用程序交互。Web应用程序的自动化通常需要使用如 Selenium 这样的工具。
    示例:Google Chrome 中打开的任何网页或基于 Web 的企业应用。
  • 基于自定义渲染引擎的应用程序
    原因:某些应用程序(如使用 Flutter、React Native 或其他跨平台框架开发的应用)可能使用自定义的渲染引擎,这些引擎不会暴露标准的Windows UI 自动化接口(如Win32 或 UIA)
    示例:企业微信、某些使用Flutter 开发的桌面应用

UI 对象检查工具

  • UISpy 是一款由微软提供的工具,专门用于 UI 自动化测试。它允许测试人员查看应用程序的UI 自动化模型,包括控件树、属性和事件。UISpy特别适用于编写UI 自动化脚本,因为它提供了一个可视化的界面来帮助测试人员选择UI 元素,并且可以模拟用户对这些UI 元素的操作。

GUI 简单示例

python 复制代码
import time
from time import sleep

from pywinauto.application import Application

# 启动应用程序
Application(backend='uia').start("notepad.exe")
time.sleep(3)
app = Application(backend='uia').connect(title_re=".*Notepad")
# 获取窗口
notepad = app.window(title_re=".*Notepad")
# 操作控件
# 输入文本
notepad.Document.type_keys("Hello, Pywinauto!")
# 关闭应用程序
notepad.close()

pywinauto 常见操作

1. 打开程序

打开应用程序

python 复制代码
start(self,cmd_line....)

部分参数详解:

cmd_line: 这是启动应用程序的命令行字符串。它必须包含应用程序的路径和名称,还可以包含启动参数

例如:

"notepad.exe":启动记事本程序

"C:\Windows\system32\notepad.exe":适用绝对路径启动记事本程序

"calc.exe":启动计算器程序

连接已经打开的应用程序

python 复制代码
connect(self,**kwargs)

部分参数详解:

process:目标的进程ID

handle:目标的窗口句柄

示例:打开sublime text程序

python 复制代码
# start 启动应用程序
Application(backend="uia").start("C:\\Program Files\\WindowsApps\\Microsoft.WindowsNotepad_11.2512.26.0_x64__8wekyb3d8bbwe\\Notepad\\notepad.exe")
# 打开sublime text 记事本
Application(backend="uia").start("D:\\Sublime Text3\\sublime_text.exe")

# 通过connect 连接已经打开的应用程序
Application(backend="uia").connect(process=24820)
# 给一个不存在的进程id
# Application(backend="uia").connect(process=66666)

app = Application(backend="uia").start(r"D:\Sublime Text3\sublime_text.exe")

# 原始字符串(r 前缀):r"D:\Sublime Text3\sublime_text.exe",r 表示 raw string,会完全忽略转义字符。
# 正斜杠 /:"D:/Sublime Text3/sublime_text.exe",Python 也能正确识别 Windows 路径。

app = Application(backend="uia").start("D:\\Sublime Text3\\sublime_text.exe")

# 打印程序的进程id
print(app.process)

Application(backend="uia").connect(process=app.process)

进程id 就是下图中的 PID

2. 定位窗口

通过pywinauto API 提供的window 方法来定位

python 复制代码
app.window(title='',..)

参数说明(参数可以组合适用):

title:文本为指定值的元素

title_re:文本匹配指定正则表达式的元素

best_match:标题于指定值相似的元素

class_name:窗口类为指定值的元素

class_name_re:类名匹配指定正则表达式的元素

python 复制代码
#start参数换成Sublime Text的⽬标路径
app = Application(backend='uia').start("D:\software\Sublime Text3\sublime_text.exe")
win = app.window(title_re='.*Sublime Text.*')
#title--精确匹配
win = app.window(title="untitled • - Sublime Text (UNREGISTERED)")
#title_re-正则匹配
win = app.window(title_re=".*Sublime.*")
#best_match-模糊匹配
win = app.window(best_match="untitled • - Sublime Text")
#class_name--精确匹配
win = app.window(class_name="PX_WINDOW_CLASS")
#class_name--正则匹配
win = app.window(class_name_re=".*WINDOW_CLASS")
win.print_control_identifiers()

class_name 就是下图中的类名称

print_control_identifiers()方法用于打印窗口及其字控件的标识符信息,帮助用户识别控件

输出内容

  • 控件的类名、标题、位置(左、上、右、下边界的坐标值)、控制类型等信息
  • 每个控件的"best match" 名称列表,这些名称可以用于引用控件
python 复制代码
# 1. 连接到指定进程的应用
app = Application(backend="uia").connect(process=21632)

# 2. 从 app 里找到对应的窗口,并赋值给变量 win
win = app.window(best_match="untitled - Sublime Text")

# 3. 等待窗口可见
win.wait("visible")

# 4. 打印窗口里所有控件信息
win.print_control_identifiers()
python 复制代码
Dialog - 'untitled - Sublime Text'    (L1131, T508, R2614, B1281)
['untitled - Sublime Text', 'Dialog', 'untitled - Sublime TextDialog']
child_window(title="untitled - Sublime Text", control_type="Window")
   | 
   | ScrollBar - ''    (L1144, T505, R1244, B515)
   | ['ScrollBar', 'ScrollBar0', 'ScrollBar1']
   |    | 
   |    | Button - '左移一列'    (L0, T0, R0, B0)
   |    | ['左移一列', '左移一列Button', 'Button', 'Button0', 'Button1']
   |    | child_window(title="左移一列", auto_id="UpButton", control_type="Button")
   |    | 
   |    | Thumb - '位置'    (L0, T0, R0, B0)
   |    | ['位置Thumb', '位置', 'Thumb', '位置Thumb0', '位置Thumb1', '位置0', '位置1', 'Thumb0', 'Thumb1']
   |    | child_window(title="位置", auto_id="ScrollbarThumb", control_type="Thumb")
   |    | 
   |    | Button - '右移一列'    (L0, T0, R0, B0)
   |    | ['右移一列', 'Button2', '右移一列Button']
   |    | child_window(title="右移一列", auto_id="DownButton", control_type="Button")
   | 
   | ScrollBar - ''    (L1044, T605, R1054, B705)
   | ['ScrollBar2']
   |    | 
   |    | Button - '上一行'    (L0, T0, R0, B0)
   |    | ['Button3', '上一行', '上一行Button']
   |    | child_window(title="上一行", auto_id="UpButton", control_type="Button")
   |    | 
   |    | Thumb - '位置'    (L0, T0, R0, B0)
   |    | ['位置Thumb2', '位置2', 'Thumb2']
   |    | child_window(title="位置", auto_id="ScrollbarThumb", control_type="Thumb")
   |    | 
   |    | Button - '下一行'    (L0, T0, R0, B0)
   |    | ['下一行Button', '下一行', 'Button4']
   |    | child_window(title="下一行", auto_id="DownButton", control_type="Button")
   | 
   | TitleBar - ''    (L1176, T511, R2601, B566)
   | ['TitleBar']
   |    | 
   |    | Menu - '系统'    (L1144, T521, R1188, B565)
   |    | ['系统', 'Menu', '系统Menu', '系统0', '系统1', 'Menu0', 'Menu1']
   |    | child_window(title="系统", auto_id="MenuBar", control_type="MenuBar")
   |    |    | 
   |    |    | MenuItem - '系统'    (L1144, T521, R1188, B565)
   |    |    | ['系统2', '系统MenuItem', 'MenuItem', 'MenuItem0', 'MenuItem1']
   |    |    | child_window(title="系统", control_type="MenuItem")
   |    | 
   |    | Button - '最小化'    (L2322, T509, R2416, B566)
   |    | ['最小化Button', 'Button5', '最小化']
   |    | child_window(title="最小化", control_type="Button")
   |    | 
   |    | Button - '最大化'    (L2416, T509, R2508, B566)
   |    | ['最大化Button', '最大化', 'Button6']
   |    | child_window(title="最大化", control_type="Button")
   |    | 
   |    | Button - '关闭'    (L2508, T509, R2602, B566)
   |    | ['Button7', '关闭', '关闭Button']
   |    | child_window(title="关闭", control_type="Button")
   | 
   | Menu - '应用程序'    (L1144, T566, R2601, B604)
   | ['Menu2', '应用程序Menu', '应用程序']
   | child_window(title="应用程序", auto_id="MenuBar", control_type="MenuBar")
   |    | 
   |    | MenuItem - '文件(F)'    (L1144, T566, R1249, B604)
   |    | ['MenuItem2', '文件(F)', '文件(F)MenuItem']
   |    | child_window(title="文件(F)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '编辑(E)'    (L1249, T566, R1354, B604)
   |    | ['MenuItem3', '编辑(E)', '编辑(E)MenuItem']
   |    | child_window(title="编辑(E)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '选择(S)'    (L1354, T566, R1460, B604)
   |    | ['选择(S)', 'MenuItem4', '选择(S)MenuItem']
   |    | child_window(title="选择(S)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '查找(I)'    (L1460, T566, R1559, B604)
   |    | ['查找(I)', 'MenuItem5', '查找(I)MenuItem']
   |    | child_window(title="查找(I)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '查看(V)'    (L1559, T566, R1667, B604)
   |    | ['MenuItem6', '查看(V)MenuItem', '查看(V)']
   |    | child_window(title="查看(V)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '转到(G)'    (L1667, T566, R1777, B604)
   |    | ['MenuItem7', '转到(G)', '转到(G)MenuItem']
   |    | child_window(title="转到(G)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '工具(T)'    (L1777, T566, R1883, B604)
   |    | ['工具(T)MenuItem', 'MenuItem8', '工具(T)']
   |    | child_window(title="工具(T)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '项目(P)'    (L1883, T566, R1990, B604)
   |    | ['项目(P)', 'MenuItem9', '项目(P)MenuItem']
   |    | child_window(title="项目(P)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '首选项(N)'    (L1990, T566, R2126, B604)
   |    | ['MenuItem10', '首选项(N)', '首选项(N)MenuItem']
   |    | child_window(title="首选项(N)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '帮助(H)'    (L2126, T566, R2237, B604)
   |    | ['帮助(H)', 'MenuItem11', '帮助(H)MenuItem']
   |    | child_window(title="帮助(H)", control_type="MenuItem")
相关推荐
小杍随笔2 小时前
【Rust 语言编程知识与应用:元编程详解】
开发语言·后端·rust
gCode Teacher 格码致知2 小时前
Javascript提高:JavaScript Promise 超通俗解释-由Deepseek产生
开发语言·javascript
小江的记录本2 小时前
【Java】Java核心关键字:final、static、volatile、synchronized、transient(附《面试高频考点》)
java·开发语言·spring boot·后端·sql·spring·面试
Wpa.wk2 小时前
APP测试 - adb基础命令2
经验分享·测试工具·adb
齐鲁大虾2 小时前
VC++ 如何获取打印机的脱机/连接状态
开发语言·c++·获取打印机状态
2301_807367192 小时前
Win10开机自启动怎么设置?关闭开机启动6大方法
开发语言·python·pygame
羊小猪~~2 小时前
【QT】--QWIdget与QDialog
开发语言·数据库·c++·后端·qt·求职招聘
Zarek枫煜2 小时前
zig与c3的算法 -- 静态队列
开发语言·stm32·单片机·嵌入式硬件·算法·51单片机
fff9811182 小时前
基于C++的爬虫框架
开发语言·c++·算法