一、需求分析与目标制定
确定目标模块:播放控制模块(播放/切换/音量调节/进度控制)、搜索功能模块、换皮肤模块、窗口大小设置模块、推荐模块,歌单管理模块等。
目标制定:对QQMusic实现回归性测试,验证核心功能的稳定性,界面交互响应等。
二、测试用例设计
设计测试用例的过程中,用例设计需要从用户的角度出发,模拟真实用户的操作流程,确保界面和功能的协同工作。

三、自动化测试
3.1 conftest.py
封装程序的启动与关闭
import time
import pytest
from pywinauto import Application
from Utils.logUtils import Logger
import psutil
class QQmusicApp:
def __init__(self):
self.app_path = "D:\\code\\code-submit\\QQmusic\\QQMusic.exe"
self.logger = Logger.get_logger()
self.app = None
self.win = None
def is_running(self):
"""判断 QQMusic 是否已运行"""
for p in psutil.process_iter(['name']):
try:
if p.info['name'] and "QQMusic" in p.info['name']:
return True
except Exception:
pass
return False
# 启动QQmusic程序
def launch(self):
try:
# self.app = Application(backend="uia").start(self.app_path)
for p in psutil.process_iter(['pid', 'name']):
try:
if p.info['name'] and "QQMusic" in p.info['name']:
self.logger.info(f"杀掉进程: {p.info['pid']}")
p.kill()
except Exception:
pass
time.sleep(2)
self.app = Application(backend="uia").start(self.app_path)
# 连接已经打开的程序
# self.app = Application(backend="uia").connect(process=12184)
# 定位窗口
self.win = self.app.window(title="QQMusic")
# 等待窗口可见
self.win.wait("visible")
self.logger.info(f"QQ应用程序启动成功!")
# self.win.print_control_identifiers()
except Exception as e:
self.logger.error(f"QQ应用程序启动失败:{e}")
# 关闭QQmusic程序
def close(self):
try:
if self.win and self.win.exists(timeout=2):
self.win.close()
except Exception:
pass
@pytest.fixture(scope="session")
def QQMusic_app():
QQmusic = QQmusicApp()
QQmusic.launch()
yield QQmusic
QQmusic.close()

所有的测试用例都共用同一个实例,scope=“session”,整个测试会话只会创建唯一 一个QQMusicApp实例,只启动一次QQMusic,所有测试用例都用这个已经启动的QQ音乐实例执行操作,所有测试执行完成后,才会关闭QQMusic。
3.2 logUtils.py
import logging
import os
import time
class InfoFilter(logging.Filter):
def filter(self, record):
return record.levelno == logging.INFO
class ErrFilter(logging.Filter):
def filter(self, record):
return record.levelno == logging.ERROR
class Logger:
# 类变量,所有对象共享该变量
logger = None
#@classmethod 的作用是将一个普通方法声明成一个类方法
@classmethod
def get_logger(cls, loggering=None):
if cls.logger is None:
cls.logger = logging.getLogger(__name__)
# 设置日志等级
cls.logger.setLevel(logging.DEBUG)
LOG_PATH = "./logs/"
if not os.path.exists(LOG_PATH):
os.mkdir(LOG_PATH)
# 创建文件 2026-6-5.log 2026-6-5_err.log 2026-6-5_info.log
now = time.strftime("%Y-%m-%d")
log_name = LOG_PATH + now + ".log"
info_logname = LOG_PATH + now + "_info.log"
err_logname = LOG_PATH + now + "_err.log"
#创建文件处理器
log_handler = logging.FileHandler(log_name, encoding="utf-8")
info_handler = logging.FileHandler(info_logname, encoding="utf-8")
# 添加文件过滤器
info_handler.addFilter(InfoFilter())
err_handler = logging.FileHandler(err_logname, encoding="utf-8")
err_handler.addFilter(ErrFilter())
# 设置文件处理器
cls.logger.addHandler(log_handler)
cls.logger.addHandler(info_handler)
cls.logger.addHandler(err_handler)
#日志输出格式
formatter = logging.Formatter(
"%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s: %(lineno)d)] – %(message)s"
)
# 设置日志输出格式
log_handler.setFormatter(formatter)
info_handler.setFormatter(formatter)
err_handler.setFormatter(formatter)
# 给logger对象添加handler
cls.logger.addHandler(log_handler)
cls.logger.addHandler(info_handler)
cls.logger.addHandler(err_handler)
return cls.logger
封装日志,所有的文件都使用同一个日志对象,便于维护,提高了阅读性,避免出现大量冗余的代码。
3.3 yamlUtils.py
import os
import yaml
def read_yaml(key):
with open(os.getcwd()+"/data/elements.yml", mode='r', encoding="utf-8") as f:
# 读取yml文件里的数据
data = yaml.safe_load(f)
return data[key]
从yaml配置文件中读取数据,把需要频繁修改的配置数据单独存起来,让代码更简洁,易维护。
YAML配置文件主要是用来存放可变的静态数据,自动化测试的控件定位,测试用例数据等。
3.4 test_common.py
import math
import time
import pytest
from pywinauto import mouse
from pywinauto.mouse import wheel_click
from Utils.yamlUtils import read_yaml
from Utils.logUtils import Logger
@pytest.mark.order(1)
class TestCommon:
Logger = Logger.get_logger()
# 测试logo
def test_logo(self, QQMusic_app):
logo_ele = read_yaml("logo")
logo = QQMusic_app.win.child_window(auto_id=logo_ele["auto_id"], control_type=logo_ele["control_type"])
logo.wait("visible")
# 测试搜索框
def test_search(self, QQMusic_app):
edit_ele = read_yaml("edit")
edit = QQMusic_app.win.child_window(auto_id=edit_ele["auto_id"], control_type=edit_ele["control_type"])
# 唤醒输入框
edit.click_input()
# 全选中之后再输入关键词就不会存在追加的情况了
edit.type_keys("^a邓紫棋")
# 测试 skin—换肤
def test_skin(self, QQMusic_app):
skin_ele = read_yaml("skin")
skin = QQMusic_app.win.child_window(auto_id=skin_ele["auto_id"], control_type=skin_ele["control_type"])
#点击换肤按钮,唤醒窗口
skin.click_input()
# 检测窗口是否存在
warning = QQMusic_app.win.child_window(title="温馨提示", control_type="Window")
warning.wait("visible")
# 获取弹窗文本
warn_text = warning.child_window(control_type="Text").window_text()
assert warn_text == "换肤功能小哥哥正在紧急支持中…"
# 关闭弹窗
warning.close()
# 测试弹窗是否正确关闭
warning.wait_not("visible")
# 测试最小化功能
def test_window_min(self, QQMusic_app):
window_min_ele = read_yaml("window_min")
window_min = QQMusic_app.win.child_window(auto_id=window_min_ele["auto_id"], control_type=window_min_ele["control_type"])
# 点击最小化按钮
window_min.click_input()
# 判断最小化是否成功
assert QQMusic_app.win.is_minimized()
# 还原窗口
QQMusic_app.win.restore()
# 测试导入音乐
def test_inport_music(self, QQMusic_app):
import_music_ele = read_yaml("import_music")
import_music_btn = QQMusic_app.win.child_window(auto_id=import_music_ele["auto_id"], control_type=import_music_ele["control_type"])
# 点击导入音乐按钮
import_music_btn.click_input()
# 定位添加本地下载音乐窗口
local_music_win = QQMusic_app.win.child_window(title="添加本地下载音乐", control_type="Window")
local_music_win.wait("visible")
# 选中所有音乐并添加
music_list = local_music_win.child_window(title="项目视图", control_type="List")
music_list.type_keys("^a{ENTER}")
# 验证是否添加成功
local_music_win.wait_not("visible")
# 测试随机播放模式(默认是随机播放,暂停模式)
def test_play_random(self, QQMusic_app):
# 定位到本地下载里的播放全部按钮
local_download_ele = read_yaml("local_download")
play_all_ele = local_download_ele["play_all"]
# 定位到播放全部按钮
play_all_btn = QQMusic_app.win.child_window(auto_id=play_all_ele["auto_id"], control_type=play_all_ele["control_type"])
for i in range(1,4):
# 点击播放全部按钮
play_all_btn.click_input()
# 定位到进度条窗口
play_control_ele = read_yaml("play_control")
total_progress_bar = play_control_ele["total_progress_bar"]
progress_bar_win = QQMusic_app.win.child_window(auto_id=total_progress_bar["auto_id"], control_type=total_progress_bar["control_type"])
# 获取progress_bar_win的尺寸大小
rect = progress_bar_win.rectangle()
x = rect.right – 3
y = math.floor((rect.top + rect.bottom) / 2)
# 鼠标点击进度条到末尾
mouse.click(coords=(x,y))
# 等待两秒,随机播放下一曲
time.sleep(2)
# 检查下一曲是否是列表中的第二首歌曲
# 若是,随机播放模式不一定错误
# 若不是,随机播放模式正确
music_name_ele = play_control_ele["music_name"]
music_name_text = QQMusic_app.win.child_window(auto_id=music_name_ele["auto_id"],
control_type=music_name_ele["control_type"]).window_text()
if music_name_text != "Andy阿杜":
self.Logger.info(f"第{i}次判断随机播放下一曲正确")
return
else:
self.Logger.error(f"第{i}次判断随机播放下一曲错误")
raise Exception("随机播放下一曲三次判断均错误")
# 测试单曲模式,默认是随机模式,上一个用例执行完是播放状态
def test_play_single(self, QQMusic_app):
# 定位到本地下载里的播放全部按钮
local_download_ele = read_yaml("local_download")
play_all_ele = local_download_ele["play_all"]
# 定位到播放全部按钮
play_all_btn = QQMusic_app.win.child_window(auto_id=play_all_ele["auto_id"],
control_type=play_all_ele["control_type"])
# 将随机模式切换为单曲模式
play_control_ele = read_yaml("play_control")
play_single = play_control_ele["play_mode"]
play_single_btn = QQMusic_app.win.child_window(auto_id=play_single["auto_id"], control_type=play_single["control_type"])
play_single_btn.click_input()
for i in range(1, 4):
# 点击播放全部按钮
play_all_btn.click_input()
# 记录切换歌曲前的歌曲名
music_name_ele = play_control_ele["music_name"]
music_name_before = QQMusic_app.win.child_window(auto_id=music_name_ele["auto_id"],
control_type=music_name_ele["control_type"]).window_text()
# 定位到进度条窗口
total_progress_bar = play_control_ele["total_progress_bar"]
progress_bar_win = QQMusic_app.win.child_window(auto_id=total_progress_bar["auto_id"],
control_type=total_progress_bar["control_type"])
# 获取progress_bar_win的尺寸大小
rect = progress_bar_win.rectangle()
x = rect.right – 3
y = math.floor((rect.top + rect.bottom) / 2)
# 鼠标点击进度条到末尾
mouse.click(coords=(x, y))
# 等待两秒,播放下一曲
time.sleep(2)
# 检查下一曲是否是切换前的歌曲
# 若是,单曲播放模式不一定正确
# 若不是,单曲播放模式一定错误
music_name_after = QQMusic_app.win.child_window(auto_id=music_name_ele["auto_id"],
control_type=music_name_ele["control_type"]).window_text()
if music_name_before != music_name_after:
self.Logger.error(f"单曲模式下下一首歌曲播放错误,music_name_before:{music_name_before},music_name_after:{music_name_after}")
break
else:
self.Logger.info(f"单曲模式下第{i}次下一首歌曲播放正确")
if i == 3:
return
raise Exception(f"单曲模式下下一首歌曲播放错误,music_name_before:{music_name_before},music_name_after:{music_name_after}")
# 测试列表模式,默认是单曲模式,上一个用例执行完是播放状态
def test_play_circle(self, QQMusic_app):
# 切换成列表模式(单曲循环—-列表模式)
play_control_ele = read_yaml("play_control")
music_name_ele = play_control_ele["music_name"]
play_single = play_control_ele["play_mode"]
play_single_btn = QQMusic_app.win.child_window(auto_id=play_single["auto_id"],
control_type=play_single["control_type"])
# 点击切换模式按钮
play_single_btn.click_input()
for i in range(1,4):
# 找到最后一首歌曲
music_list_ele = read_yaml("music_list")
music_list = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
# 获取歌曲列表的中间坐标
list_mid = music_list.rectangle().mid_point()
# 鼠标下拉滑动框
mouse.scroll(coords=(list_mid.x, list_mid.y), wheel_dist=–500)
# 获取列表中项目的数量
list_size = music_list.item_count()
# 定位到最后一首歌曲的位置
last_music_mid = music_list.get_item(row=list_size–1).rectangle().mid_point()
# 双击最后一首歌曲,使其播放
mouse.double_click(coords=(last_music_mid.x, last_music_mid.y))
# 定位到进度条窗口
total_progress_bar = play_control_ele["total_progress_bar"]
progress_bar_win = QQMusic_app.win.child_window(auto_id=total_progress_bar["auto_id"],
control_type=total_progress_bar["control_type"])
# 获取progress_bar_win的尺寸大小
rect = progress_bar_win.rectangle()
x = rect.right – 3
y = math.floor((rect.top + rect.bottom) / 2)
# 鼠标点击进度条到末尾
mouse.click(coords=(x, y))
# 等待两秒,播放下一曲
time.sleep(2)
# 列表模式下校验,下一首歌曲是否是列表中第一个歌曲
# 若是,列表循环校验不一定正确
# 若不是,列表循环校验一定错误
music_name = QQMusic_app.win.child_window(auto_id=music_name_ele["auto_id"],
control_type=music_name_ele["control_type"]).window_text()
if music_name != "2002年的第一场雪":
self.Logger.error(f"列表循环模式下一首歌曲校验错误,music_name:{music_name}")
break;
else:
self.Logger.info(f"第{i}次列表循环下一首歌曲校验正确")
if i == 3:
return
raise Exception(f"列表循环模式下一首歌曲校验错误,music_name:{music_name}")
通过读取yaml文件中的控件数据进行定位,完成对于QQMusic的 logo主题、播放模式、导入音乐、最小化、搜索功能、换肤功能的测试。
3.5 test_recommend.py
import time
import pytest
from pywinauto import mouse
from Utils.yamlUtils import read_yaml
@pytest.mark.order(2)
class TestRecommend:
# 测试推荐窗口
def test_rec_text(self, QQMusic_app):
# 定位到推荐窗口
rec_text_ele = read_yaml("recommend")
rec_btn = QQMusic_app.win.child_window(auto_id=rec_text_ele["auto_id"],
control_type=rec_text_ele["control_type"])
# 点击推荐页
rec_btn.click_input()
# 校验推荐文本是否正确
rec_text = rec_text_ele["recommend_text"]
rec_text_btn = QQMusic_app.win.child_window(auto_id=rec_text["auto_id"],
control_type=rec_text["control_type"])
assert rec_text_btn.window_text() == "推荐"
# 校验今日为你推荐文本是否正确
rec_foru_text = rec_text_ele["rec_foru_text"]
rec_foru_btn = QQMusic_app.win.child_window(auto_id=rec_foru_text["auto_id"],
control_type=rec_foru_text["control_type"])
assert rec_foru_btn.window_text()
# 校验你的音乐补给文本是否正确
music_supply_text = rec_text_ele["music_supply_text"]
music_supply_btn = QQMusic_app.win.child_window(auto_id=music_supply_text["auto_id"],
control_type=music_supply_text["control_type"])
assert music_supply_btn.window_text() == "你的音乐补给"
# 测试推荐界面的左滑动窗口
def test_recforu_scroll_left(self, QQMusic_app):
rec_test_ele = read_yaml("recommend")
# 获取今日为你推荐第一项文本
rec_foru_ftext_ele =rec_test_ele["rec_foru_first_text"]
rec_foru_ftext_before = (QQMusic_app.win.child_window(auto_id=rec_foru_ftext_ele["auto_id"],
control_type=rec_foru_ftext_ele["control_type"],
found_index=0).window_text())
# 点击向左滑动按钮
rec_foru_lscroll_ele = rec_test_ele["rec_foru_left_scroll"]
rec_foru_lscroll_btn = QQMusic_app.win.child_window(auto_id=rec_foru_lscroll_ele["auto_id"],
control_type=rec_foru_lscroll_ele["control_type"])
rec_foru_lscroll_btn.click_input()
# 获取滑动之后今日为你推荐的第一项文本
rec_foru_ftext_after = (QQMusic_app.win.child_window(auto_id=rec_foru_ftext_ele["auto_id"],
control_type=rec_foru_ftext_ele["control_type"],
found_index=0).window_text())
# 校验滑动前与滑动后文本是否不一致
assert rec_foru_ftext_before != rec_foru_ftext_after
# 测试推荐界面的右滑动窗口
def test_recforu_scroll_right(self, QQMusic_app):
rec_test_ele = read_yaml("recommend")
# 获取今日为你推荐第一项文本
rec_foru_ftext_ele = rec_test_ele["rec_foru_first_text"]
rec_foru_ftext_before = (QQMusic_app.win.child_window(auto_id=rec_foru_ftext_ele["auto_id"],
control_type=rec_foru_ftext_ele["control_type"],
found_index=0).window_text())
# 点击向右滑动按钮
rec_foru_rscroll_ele = rec_test_ele["rec_foru_right_scroll"]
rec_foru_rscroll_btn = QQMusic_app.win.child_window(auto_id=rec_foru_rscroll_ele["auto_id"],
control_type=rec_foru_rscroll_ele["control_type"])
rec_foru_rscroll_btn.click_input()
# 获取滑动之后今日为你推荐的第一项文本
rec_foru_ftext_after = (QQMusic_app.win.child_window(auto_id=rec_foru_ftext_ele["auto_id"],
control_type=rec_foru_ftext_ele["control_type"],
found_index=0).window_text())
# 校验滑动前与滑动后文本是否不一致
assert rec_foru_ftext_before != rec_foru_ftext_after
# 测试音乐补给左滚动按钮
def test_music_scroll_left(self, QQMusic_app):
rec_ele = read_yaml("recommend")
# 将下拉框进行下拉
entire_music_area_ele = rec_ele["entire_rec_module"]
entire_music_area = QQMusic_app.win.child_window(auto_id=entire_music_area_ele["auto_id"],
control_type=entire_music_area_ele["control_type"])
point = entire_music_area.rectangle().mid_point()
mouse.scroll(coords=(point.x, point.y), wheel_dist=–500)
# 获取滑动之前的项目文本
fline_text_ele = rec_ele["music_area_fline_text"]
fline_text_before = (QQMusic_app.win.child_window(auto_id=fline_text_ele["auto_id"],
control_type=fline_text_ele["control_type"],
found_index=0).window_text())
sline_text_ele = rec_ele["music_area_sline_text"]
sline_text_before = (QQMusic_app.win.child_window(auto_id=sline_text_ele["auto_id"],
control_type=sline_text_ele["control_type"],
found_index=0).window_text())
# 定位左滑动按钮
left_scroll_ele = rec_ele["music_supply_left_scroll"]
left_scroll_btn = QQMusic_app.win.child_window(auto_id=left_scroll_ele["auto_id"],
control_type=left_scroll_ele["control_type"])
# 点击左滑动按钮
left_scroll_btn.click_input()
# 获取滑动之后的项目文本
fline_text_after = (QQMusic_app.win.child_window(auto_id=fline_text_ele["auto_id"],
control_type=fline_text_ele["control_type"],
found_index=0).window_text())
sline_text_after = (QQMusic_app.win.child_window(auto_id=sline_text_ele["auto_id"],
control_type=sline_text_ele["control_type"],
found_index=0).window_text())
# 校验滑动之前与滑动之后是否发生了变化
assert fline_text_before != fline_text_after
assert sline_text_before != sline_text_after
# 测试音乐补给右滚动按钮
def test_music_scroll_right(self, QQMusic_app):
rec_ele = read_yaml("recommend")
# 将下拉框进行下拉
entire_music_area_ele = rec_ele["entire_rec_module"]
entire_music_area = QQMusic_app.win.child_window(auto_id=entire_music_area_ele["auto_id"],
control_type=entire_music_area_ele["control_type"])
point = entire_music_area.rectangle().mid_point()
mouse.scroll(coords=(point.x, point.y), wheel_dist=–500)
# 获取滑动之前的项目文本
fline_text_ele = rec_ele["music_area_fline_text"]
fline_text_before = (QQMusic_app.win.child_window(auto_id=fline_text_ele["auto_id"],
control_type=fline_text_ele["control_type"],
found_index=0).window_text())
sline_text_ele = rec_ele["music_area_sline_text"]
sline_text_before = (QQMusic_app.win.child_window(auto_id=sline_text_ele["auto_id"],
control_type=sline_text_ele["control_type"],
found_index=0).window_text())
# 定位右滑动按钮
right_scroll_ele = rec_ele["music_supply_right_scroll"]
right_scroll_btn = QQMusic_app.win.child_window(auto_id=right_scroll_ele["auto_id"],
control_type=right_scroll_ele["control_type"])
# 点击右滑动按钮
right_scroll_btn.click_input()
# 获取滑动之后的项目文本
fline_text_after = (QQMusic_app.win.child_window(auto_id=fline_text_ele["auto_id"],
control_type=fline_text_ele["control_type"],
found_index=0).window_text())
sline_text_after = (QQMusic_app.win.child_window(auto_id=sline_text_ele["auto_id"],
control_type=sline_text_ele["control_type"],
found_index=0).window_text())
# 校验滑动之前与滑动之后是否发生了变化
assert fline_text_before != fline_text_after
assert sline_text_before != sline_text_after
测试推荐页面、今日为你推荐以及你的音乐补给文本是否正确和今日为你推荐、你的音乐补给左滑动,右滑动按钮是否功能正常。
3.6 test_localdown.py
import math
import time
import pytest
from pywinauto import mouse
from Utils.yamlUtils import read_yaml
@pytest.mark.order(3)
class TestLocal:
# 测试本地下载界面的"本地音乐","歌曲名称","歌手名称","专辑名称"文本
def test_local_text(self, QQMusic_app):
# 定位导航栏里的本地下载
localdown_ele = read_yaml("local_download")
localdown_label = QQMusic_app.win.child_window(auto_id=localdown_ele["auto_id"],
control_type=localdown_ele["control_type"])
# 点击本地下载标签
localdown_label.click_input()
# 测试本地音乐文本
local_music_text_ele = localdown_ele["local_music_text"]
local_music_text = (QQMusic_app.win.child_window(auto_id=local_music_text_ele["auto_id"],
control_type=local_music_text_ele["control_type"])
.window_text())
assert local_music_text == "本地音乐"
# 测试歌曲名称文本
song_text_ele = localdown_ele["song_name"]
song_text = (QQMusic_app.win.child_window(auto_id=song_text_ele["auto_id"],
control_type=song_text_ele["control_type"])
.window_text())
assert song_text == "歌曲名称"
# 测试歌手名称文本
singer_text_ele = localdown_ele["singer_name"]
singer_text = (QQMusic_app.win.child_window(auto_id=singer_text_ele["auto_id"],
control_type=singer_text_ele["control_type"])
.window_text())
assert singer_text == "歌手名称"
# 测试专辑名称文本
Album_title_ele = localdown_ele["Album_title"]
Album_text = (QQMusic_app.win.child_window(auto_id=Album_title_ele["auto_id"],
control_type=Album_title_ele["control_type"])
.window_text())
assert Album_text == "专辑名称"
def test_local_playAll(self, QQMusic_app):
# 定位到播放全部按钮
play_control_ele = read_yaml("local_download")
play_all_ele = play_control_ele["play_all"]
play_all_btn = QQMusic_app.win.child_window(auto_id=play_all_ele["auto_id"],
control_type=play_all_ele["control_type"])
# 点击播放全部按钮
play_all_btn.click_input()
# 获取当前进度条的进度
real_progress_bar_ele = read_yaml("play_control")["real_progress_bar"]
real_progress_bar_before = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_before = real_progress_bar_before.rectangle().right
# 等待两秒
time.sleep(2)
# 获取新的进度条的进度
real_progress_bar_after = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_after = real_progress_bar_after.rectangle().right
assert progress_bar_len_before != progress_bar_len_after
# 测试歌曲列表中双击歌曲进行播放
def test_local_playSingle(self, QQMusic_app):
# 定位到歌曲列表
music_list_ele = read_yaml("music_list")
music_list = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
point = music_list.rectangle().mid_point()
mouse.scroll(coords=(point.x, point.y), wheel_dist=500)
# 获取歌曲列表的项目数
if music_list.item_count() <= 0:
assert 0,"歌曲列表为空"
# 选择一首歌曲进行双击播放
point = music_list.get_item(row=0).rectangle().mid_point()
mouse.double_click(coords=(point.x, point.y))
# 获取当前进度条的进度
real_progress_bar_ele = read_yaml("play_control")["real_progress_bar"]
real_progress_bar_before = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_before = real_progress_bar_before.rectangle().right
# 等待两秒
time.sleep(2)
# 获取新的进度条的进度
real_progress_bar_after = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_after = real_progress_bar_after.rectangle().right
assert progress_bar_len_before != progress_bar_len_after
# 将歌曲列表中的歌曲全部添加到我喜欢列表中
def test_mark_like(self, QQMusic_app):
# 定位到歌曲列表
music_list_ele = read_yaml("music_list")
music_list = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
# 获取歌曲列表中的项目数
music_list_size = music_list.item_count()
print("music_list_size: "+ str(music_list_size))
# 将全部歌曲标记为我喜欢
for i in range(0, music_list_size):
if i != 0 and i % 6 == 0:
# 下拉框进行下拉
point = music_list.rectangle().mid_point()
mouse.scroll(coords=(point.x, point.y), wheel_dist=–10)
time.sleep(1)
# 获取爱心中间的位置
rect = music_list.get_item(row=i).rectangle()
x = rect.left + 22
y = math.floor((rect.top + rect.bottom) / 2)
mouse.click(coords=(x,y))
测试本地下载页面文本是否正确以及播放全部按钮,播放歌曲,将歌曲标记为我喜欢功能是否正确。
3.7 test_like.py
import math
import time
import pytest
from pywinauto import mouse
from Utils.yamlUtils import read_yaml
@pytest.mark.order(4)
class TestLike:
# 测试我喜欢界面
def test_like_text(self, QQMusic_app):
# 定位导航栏中的"我喜欢"
self_like_ele = read_yaml("self_like")
self_like_btn = QQMusic_app.win.child_window(auto_id=self_like_ele["auto_id"],
control_type=self_like_ele["control_type"])
# 点击我喜欢
self_like_btn.click_input()
# 校验我喜欢文本
self_like_text_ele = self_like_ele["self_like_text"]
self_like_text = (QQMusic_app.win.child_window(auto_id=self_like_text_ele["auto_id"],
control_type=self_like_text_ele["control_type"])
.window_text())
assert self_like_text == "我喜欢"
# 测试歌曲名称文本
song_text_ele = self_like_ele["song_name"]
song_text = (QQMusic_app.win.child_window(auto_id=song_text_ele["auto_id"],
control_type=song_text_ele["control_type"])
.window_text())
assert song_text == "歌曲名称"
# 测试歌手名称文本
singer_text_ele = self_like_ele["singer_name"]
singer_text = (QQMusic_app.win.child_window(auto_id=singer_text_ele["auto_id"],
control_type=singer_text_ele["control_type"])
.window_text())
assert singer_text == "歌手名称"
# 测试专辑名称文本
Album_title_ele = self_like_ele["Album_title"]
Album_text = (QQMusic_app.win.child_window(auto_id=Album_title_ele["auto_id"],
control_type=Album_title_ele["control_type"])
.window_text())
assert Album_text == "专辑名称"
# 测试我喜欢界面的播放全部按钮
def test_like_playAll(self, QQMusic_app):
# 定位到播放全部按钮
play_all_ele = read_yaml("self_like")["play_all"]
play_all_btn = QQMusic_app.win.child_window(auto_id=play_all_ele["auto_id"],
control_type=play_all_ele["control_type"])
# 点击播放全部按钮
play_all_btn.click_input()
# 获取当前进度条的进度
real_progress_bar_ele = read_yaml("play_control")["real_progress_bar"]
real_progress_bar_before = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_before = real_progress_bar_before.rectangle().right
# 等待两秒
time.sleep(2)
# 获取新的进度条的进度
real_progress_bar_after = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_after = real_progress_bar_after.rectangle().right
assert progress_bar_len_before != progress_bar_len_after
# 测试我喜欢界面的歌曲双击播放功能
def test_like_playSingle(self, QQMusic_app):
# 定位到歌曲列表
music_list_ele = read_yaml("self_like")["music_list"]
music_list = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
# 获取歌曲列表的项目数
if music_list.item_count() <= 0:
assert 0, "歌曲列表为空"
# 选择一首歌曲进行双击播放
point = music_list.get_item(row=0).rectangle().mid_point()
mouse.double_click(coords=(point.x, point.y))
# 获取当前进度条的进度
real_progress_bar_ele = read_yaml("play_control")["real_progress_bar"]
real_progress_bar_before = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_before = real_progress_bar_before.rectangle().right
# 等待两秒
time.sleep(2)
# 获取新的进度条的进度
real_progress_bar_after = QQMusic_app.win.child_window(auto_id=real_progress_bar_ele["auto_id"],
control_type=real_progress_bar_ele["control_type"])
progress_bar_len_after = real_progress_bar_after.rectangle().right
assert progress_bar_len_before != progress_bar_len_after
# 测试我喜欢界面取消标记喜欢的功能
def test_mark_unLike(self, QQMusic_app):
# 定位到歌曲列表
music_list_ele = read_yaml("self_like")["music_list"]
music_list_before = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
# 获取到歌曲列表中歌曲的数量
list_size_before = music_list_before.item_count()
# 取消标记喜欢
rect = music_list_before.get_item(row=0).rectangle()
x = rect.left + 22
y = math.floor((rect.top + rect.bottom) / 2)
mouse.click(coords=(x, y))
# 进行校验
music_list_after = QQMusic_app.win.child_window(auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"])
list_size_after = music_list_after.item_count()
assert list_size_before == list_size_after + 1
测试我喜欢页面的文本以及播放全部按钮,播放歌曲,取消标记喜欢功能是否正确。
3.8 test_lyrics.py
import time
import pytest
from Utils.yamlUtils import read_yaml
from conftest import QQMusic_app
@pytest.mark.order(5)
class TestLyrics:
def test_lyrics_text(self, QQMusic_app):
# 定位到歌词入口按钮
lyrics_portal_ele = read_yaml("lyrics_portal")
lyrics_portal_btn = QQMusic_app.win.child_window(auto_id=lyrics_portal_ele["auto_id"],
control_type=lyrics_portal_ele["control_type"])
lyrics_portal_btn.wait('visible', timeout=10)
lyrics_portal_btn.wait('enabled', timeout=10)
btn = lyrics_portal_btn.wrapper_object()
btn.click_input()
# 点击歌词入口按钮
# lyrics_portal_btn.click_input()
# 获取歌手文本
singer_text_ele = lyrics_portal_ele["singer_title_text"]
singer_text = (QQMusic_app.win.child_window(auto_id=singer_text_ele["auto_id"],
control_type=singer_text_ele["control_type"])
.window_text())
# 校验歌手文本
assert singer_text == "刀郎"
# 获取歌名文本
song_text_ele = lyrics_portal_ele["song_title_text"]
song_text = (QQMusic_app.win.child_window(auto_id=song_text_ele["auto_id"],
control_type=song_text_ele["control_type"])
.window_text())
# 校验歌名文本
assert song_text == "2002年的第一场雪"
def test_lyrics_page(self, QQMusic_app):
# 收起歌词页面
hide_lyrics_ele = read_yaml("lyrics_portal")["hide_lyrics"]
hide_lyrics_btn = QQMusic_app.win.child_window(auto_id=hide_lyrics_ele["auto_id"],
control_type=hide_lyrics_ele["control_type"])
hide_lyrics_btn.click_input()
# 加行代码,点击我喜欢
self_like_ele = read_yaml("self_like")
self_like_btn = QQMusic_app.win.child_window(auto_id=self_like_ele["auto_id"],
control_type=self_like_ele["control_type"])
self_like_btn.click_input()
# 为后面的测试用例做准备—-点击播放歌曲并立即暂停
# 播放全部按钮
play_all_ele = read_yaml("self_like")["play_all"]
play_all_btn = QQMusic_app.win.child_window(auto_id=play_all_ele["auto_id"],
control_type=play_all_ele["control_type"])
time.sleep(2)
# 点击播放全部按钮
play_all_btn.click_input()
# 定位到播放按钮
play_ele = read_yaml("play_control")["play"]
play_btn = QQMusic_app.win.child_window(auto_id=play_ele["auto_id"],
control_type=play_ele["control_type"])
# 点击播放按钮
play_btn.click_input()
# 获取歌手名和歌曲名
play_control_ele = read_yaml("play_control")
singer_name_ele = play_control_ele["singer_name"]
music_name_ele = play_control_ele["music_name"]
singer_name = (QQMusic_app.win.child_window(auto_id=singer_name_ele["auto_id"],
control_type=singer_name_ele["control_type"])
.window_text())
music_name = (QQMusic_app.win.child_window(auto_id=music_name_ele["auto_id"],
control_type=music_name_ele["control_type"])
.window_text())
# lyrics_text = f"{music_name} – {singer_name}"
# 定位到歌词入口按钮
lyrics_portal_ele = read_yaml("lyrics_portal")
lyrics_portal_btn = QQMusic_app.win.child_window(auto_id=lyrics_portal_ele["auto_id"],
control_type=lyrics_portal_ele["control_type"])
# 点击歌词入口按钮
lyrics_portal_btn.click_input()
# 定位到歌词列表
lyrics_list_ele = lyrics_portal_ele["lyrics_list"]
lyrics_list = QQMusic_app.win.child_window(auto_id=lyrics_list_ele["auto_id"],
control_type=lyrics_list_ele["control_type"])
for i in lyrics_list.children():
if i.window_text() in music_name or i.window_text() in singer_name:
return
# print(f"{i.window_text()}")
# 说明没有匹配成功
raise Exception(f"歌词匹配失败,music_name: {music_name}, singer_name:{singer_name}")
测试歌词页面文本是否正确,以及歌词入口按钮,收起歌词按钮,歌词列表是否符合预期。
3.9 elements.yml
logo:
auto_id: QQMusic.background.head.headLeft.logo
control_type: Text
edit:
auto_id: QQMusic.background.head.headRight.searchBox.lineEdit
control_type: Edit
skin:
auto_id: QQMusic.background.head.headRight.settingBox.skin
control_type: Button
window_min:
auto_id: QQMusic.background.head.headRight.settingBox.min
control_type: Button
import_music:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.addLocal
control_type: Button
local_download:
auto_id: QQMusic.background.body.bodyLeft.leftBox.myMusic.local.btStyle
control_type: Group
play_all:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.musicPlayBox.playAll.playAllBtn
control_type: Button
# 本地音乐
local_music_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.PageTittle
control_type: Text
# 歌曲名称
song_name:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicNameLabel
control_type: Text
# 歌手名称
singer_name:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicSingerLabel
control_type: Text
# 专辑名称
Album_title:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicAlbumLabel
control_type: Text
play_control:
total_progress_bar:
auto_id: QQMusic.background.body.bodyRight.progressBar.inLine
control_type: Custom
real_progress_bar:
auto_id: QQMusic.background.body.bodyRight.progressBar.outLine
control_type: Custom
music_name:
auto_id: QQMusic.background.body.bodyRight.controlBox.play1.musicName
control_type: Text
singer_name:
auto_id: QQMusic.background.body.bodyRight.controlBox.play1.musicSinger
control_type: Text
play_mode:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.playMode
control_type: Button
play:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.play
control_type: Button
music_list:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.localPage.pageMusicList
control_type: List
recommend:
auto_id: QQMusic.background.body.bodyLeft.leftBox.onlineMusic.rec.btStyle
control_type: Group
recommend_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.recText
control_type: Text
rec_foru_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.recMusicText
control_type: Text
music_supply_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.supplyMusicText
control_type: Text
rec_foru_left_scroll:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.recMusicBox.leftPage
control_type: Group
rec_foru_right_scroll:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.recMusicBox.rightPage
control_type: Group
rec_foru_first_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.recMusicBox.musicContent.recListUp.RecBoxItem.recBoxItemText
control_type: Text
music_supply_left_scroll:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.supplyMuscBox.leftPage
control_type: Group
music_supply_right_scroll:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.supplyMuscBox.rightPage
control_type: Group
music_area_fline_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.supplyMuscBox.musicContent.recListUp.RecBoxItem.recBoxItemText
control_type: Text
music_area_sline_text:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollarea_viewport.scrollAreaWidgetContents_2.supplyMuscBox.musicContent.recListDown.RecBoxItem.recBoxItemText
control_type: Text
entire_rec_module:
auto_id: QQMusic.background.body.bodyRight.stackedWidget
control_type: Custom
self_like:
auto_id: QQMusic.background.body.bodyLeft.leftBox.myMusic.like.btStyle
control_type: Group
self_like_text:
auto_id: QQMusic.background.body.bodyLeft.leftBox.myMusic.like.btStyle.btText
control_type: Text
# 歌曲名称
song_name:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicNameLabel
control_type: Text
# 歌手名称
singer_name:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicSingerLabel
control_type: Text
# 专辑名称
Album_title:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicAlbumLabel
control_type: Text
play_all:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.likePage.musicPlayBox.playAll.playAllBtn
control_type: Button
music_list:
auto_id: QQMusic.background.body.bodyRight.stackedWidget.likePage.pageMusicList
control_type: List
# 歌词入口
lyrics_portal:
auto_id: QQMusic.background.body.bodyRight.controlBox.play3.lrcWord
control_type: Button
singer_title_text:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.titleBox.musicSinger
control_type: Text
song_title_text:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.titleBox.musicName
control_type: Text
# 收起歌词
hide_lyrics:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.hideBtn
control_type: Button
lyrics_list:
auto_id: QQMusic.LrcPage.bgStyle.lrcContent
control_type: Group
yaml文件中存放的控件数据,用来辅助定位控件,提高代码维护性与可读性。
3.10 pytest.ini
[pytest]
addopts = –vs –p no:faulthandler
pytest 的配置文件,用来便于pytest进行测试。
3.11 requirements.txt
pywinauto==0.6.9
allure–pytest==2.13.5
pytest==8.3.2
pytest–order==1.4.0
PyYAML==6.0.1
测试需要使用到的库。
四、测试报告

五、项目成果
成功维护了QQMusic核心功能的自动化测试覆盖,构建了可维护,可扩展的自动化测试框架,提升了测试效率,单次完整测试执行时间从手动的30分钟缩短至一分钟,实现了每日自动化回归测试,为Window桌面应用自动化测试提供了成熟的实践方案。

