当前位置: 首页 > news >正文

用 Python 写的自动化测试 WPF 程序的一个案例

背景

      在日常开发和测试工作中,我们需要对 WPF 桌面应用程序进行功能验证。传统的手工点击方式效率低、容易出错,特别是一些需要重复执行的操作,人工测试不仅耗时,而且难以保证一致性。
因此,我尝试用 Python 来实现自动化测试,通过脚本来启动和操作 WPF 程序,完成一些表单填写和操作的验证。

遇到的问题

在实现过程中,主要遇到几个问题:

  1. 进程连接困难
    一开始通过 psutil 找到目标进程 PID 没问题,但在用 pywinauto 连接时,总是提示 Process with PID=xxxx not found
    原因是目标进程虽然启动了,但窗口还没完全初始化,或者运行权限不一致。

  2. 控件识别不稳定
    WPF 程序的控件有时用 uia 后端才能识别,有时需要 win32。一开始只用单一后端会导致找不到控件。

  3. 执行速度与同步问题
    自动化脚本运行太快,程序界面还没加载完就开始操作,导致控件查找失败

解决思路

针对以上问题,我采取了以下思路:

  1. 封装进程连接方法
    写了 get_app_window 方法,先用 psutil 找到目标进程,再用 pywinauto.Application.connect 连接窗口,并增加了 backend(默认为 uia)和 wait_time(默认为 5 秒)的参数,确保能灵活切换后端并等待窗口加载完成。

  2. 默认值和重试机制
    在方法内部设置默认参数,避免每次调用都要传。并预留了超时和重试机制,保证在窗口加载慢时也能成功。

  3. 封装操作逻辑
    对常用操作(如填写表单)进行了函数封装,比如 fill_subject_form(),这样测试用例更清晰,后续要换别的数据也很方便。

具体方案

代码如下:

import psutil
import time
import os
from pywinauto import Application
from pywinauto.mouse import clickdef get_app_window(process_name: str, backend: str = "uia", wait_time: float = 5.0):pid = Nonefor proc in psutil.process_iter(['pid', 'name']):if proc.info['name'] and proc.info['name'].lower() == process_name.lower():pid = proc.info['pid']breakif not pid:raise RuntimeError(f"未找到进程:{process_name}")# 直接用默认 backend 和 wait_timeapp = Application(backend=backend).connect(process=pid, timeout=wait_time)win = app.top_window()return windef generate_incremental_num(counter_file="counter.txt"):"""递增编号生成器,返回 3 位字符串编号"""if not os.path.exists(counter_file):with open(counter_file, "w") as f:f.write("1")with open(counter_file, "r") as f:num = int(f.read().strip())new_num = num + 1with open(counter_file, "w") as f:f.write(str(new_num))return f"{num:03d}"  # 格式化为 '001'def select_position(win, main_area, sub_area=None):"""点击主部位和子部位按钮"""try:win.child_window(title=main_area, control_type="Button").click_input()print(f"✅ 已点击主部位:{main_area}")time.sleep(0.5)if sub_area:win.child_window(title=sub_area, control_type="Button").click_input()print(f"✅ 已点击子部位:{sub_area}")except Exception as e:print(f"❌ 点击部位失败:{e}")def wait_for_checkboxes(win, timeout=5):"""等待复选框出现"""for _ in range(timeout * 2):  # 每0.5秒查一次checkboxes = win.descendants(control_type="CheckBox")if len(checkboxes) > 1:return checkboxestime.sleep(0.5)raise RuntimeError("超时:复选框仍未出现")def click_first_row_of_patient_grid(win):"""模拟点击 patientGrid 第一行(通过坐标)"""try:grid = win.child_window(auto_id="patientGrid", control_type="DataGrid")rect = grid.rectangle()# 假设表头约 40 像素,点击第 1 行中间位置x = rect.left + 20y = rect.top + 50  # 跳过表头高度from pywinauto.mouse import clickclick(button='left', coords=(x, y))print(f"✅ 已模拟点击 patientGrid 第一行坐标 ({x}, {y})")time.sleep(0.5)except Exception as e:import tracebackprint(f"❌ 点击 patientGrid 第一行失败:{e}")traceback.print_exc()def click_detect_button(win):try:btnExamination = win.child_window(auto_id="btnExamination", control_type="Button")btnExamination.click_input()print("✅ 已点击“检测”按钮")except Exception as e:print(f"❌ 点击“检测”失败:{e}")def perform_detection_action(win):"""在检测界面依次点击 btnLive 和 btnSnap"""try:print("🎬 开始检测页面点击操作...")# 点击 btnLivefor i in range(10000):print(f"\n🔁 第 {i + 1}/10000 次执行...")btn_live = win.child_window(auto_id="btnLive", control_type="Button")btn_live.click_input()print("✅ 已点击 btnLive")time.sleep(10)  # 等待 8 秒# 点击 btnSnapbtn_snap = win.child_window(auto_id="btnSnap", control_type="Button")btn_snap.click_input()print("✅ 已点击 btnSnap")time.sleep(8)# 点击 btnZstackFastbtn_zstack = win.child_window(auto_id="btnZstackFast", control_type="Button")btn_zstack.click_input()print("✅ 已点击 btnZstackFast")time.sleep(60)except Exception as e:print(f"❌ 检测页面按钮点击失败:{e}")def fill_subject_form(win, name, num, sex, age, height, weight, project, main_area, sub_area):print("⏳ 正在填写表单...")# 点击“新增”try:btnAdd = win.child_window(auto_id="btnAdd", control_type="Button")btnAdd.click_input()print("✅ 已点击“新增”按钮")time.sleep(1.0)except:raise RuntimeError("❌ 找不到“新增”按钮")# 获取 Edit 控件组edits = win.descendants(control_type="Edit")# 姓名win.child_window(auto_id="_SubjectNameTextBox_", control_type="Edit").set_edit_text(name)# 编号edits[1].set_edit_text(num)# 年龄、身高、体重edits[2].set_edit_text(str(age))edits[3].set_edit_text(str(height))edits[4].set_edit_text(str(weight))# 受试部位(文本框 + 按钮点击)win.child_window(auto_id="PositionResult", control_type="Edit").set_edit_text(sub_area)select_position(win, main_area, sub_area)# 点击“确认”btnOk = win.child_window(auto_id="btnOk", control_type="Button")btnOk.click_input()print(f"✅ 编号 {num} 提交完成")time.sleep(2)win = get_app_window("780.exe")# 等复选框加载完成,再点击“检测”click_first_row_of_patient_grid(win)click_detect_button(win)time.sleep(1.5)# 再次获取当前窗口(检测界面可能为新窗口)detect_win = get_app_window("780.exe")perform_detection_action(detect_win)if __name__ == "__main__":try:win = get_app_window("780.exe")# 获取自动编号num = generate_incremental_num()# 调用填写函数fill_subject_form(win=win,name="张三",num=num,sex="Male",age=30,height=175,weight=70,project="111",main_area="头颈部",sub_area="左面颊")except Exception as e:print(f"❌ 自动化失败:{e}")

总结

      通过这个小案例,我基本跑通了 Python + psutil + pywinauto 的组合,解决了“如何定位 WPF 窗口并自动化操作”的问题。

http://www.dtcms.com/a/342121.html

相关文章:

  • Jmeter接口测试之文件上传
  • XXL-Job REST API 工具类完全解析:简化分布式任务调度集成
  • WebSocket和跨域问题
  • Android为ijkplayer设置音频发音类型usage
  • 如何用 SolveigMM Video Splitter 从视频中提取 AAC 音频
  • CMake3: CMake的嵌套使用与自定义库
  • Spring Event 企业级应用
  • 笔试——Day45
  • Prompt魔法:提示词工程与ChatGPT行业应用读书笔记:提示词设计全能指南
  • 第四章:大模型(LLM)】07.Prompt工程-(7)角色提示
  • Flink基础
  • 解锁工业级Prompt设计,打造高准确率AI应用
  • Web自动化测试:测试用例流程设计
  • Java设计模式-解释器模式
  • 策略模式 vs 适配器模式
  • 基于STM32设计的大棚育苗管理系统(4G+华为云IOT)_265
  • 移动应用抓包与调试实战 Charles工具在iOS和Android中的应用
  • 数据结构初阶:详解二叉树(三):链式二叉树
  • system\core\init\init.cpp----LoadBootScripts()解析init.rc(1)
  • STM32之串口详解
  • 学习Linux嵌入式(正点原子imx课程)开发到底是在学什么
  • Spring Cloud Netflix学习笔记06-Zuul
  • Kafka消息持久化机制全解析:存储原理与实战场景
  • Kafka集成Flume
  • 人工智能 -- 循环神经网络day1 -- 自然语言基础、NLP基础概率、NLP基本流程、NLP特征工程、NLP特征输入
  • 算法 之 拓 扑 排 序
  • LeetCode 回文链表
  • 桥梁设计模式
  • RabbitMQ事务消息原理是什么
  • RabbitMQ:延时消息(死信交换机、延迟消息插件)