1、需求分析与目标制定
确定目标模块:音乐播放控制(播放/暂停/切歌)、搜索功能(文字输入、智能推荐)、歌单管理(创建/删除/排序)、用户交互(评论/分享)等
目标制定:对QQ音乐实现回归测试,验证核心功能的稳定性、界面交互响应等
2、测试用例设计
设计用例过程中,测试人员需要综合考虑界面和功能的交互关系。用例设计需要从用户的角度出发,模拟真实用户的操作流程,确保界面和功能的协同工作。



conftest.py
【封装程序启动与关闭】
python
import pytest
from pywinauto import Application
from Utils.logUtils import Logger
class QQmusicApp:
def __init__(self): #定义类的构造方法,创建这个类的实例的时候会自动执行
self.app_path = r"D:\file\qqmusic\qqmusic\QQMusic.exe"
self.logger = Logger.getlog() #日志
self.app = None #存储pywinauto.Application实例
self.win = None #存储窗口对象
#启动QQmusic程序
def launch(self):
try: #异常捕获的开始,捕获启动过程中可能出现的错误
self.app = Application(backend="uia").start(self.app_path)
#测试代码
# self.app = Application(backend="uia").connect(process=4060)
#定位窗⼝
self.win = self.app.window(title="QQMusic")
self.win.wait("visible")
self.logger.info("应⽤程序启动成功!")
# self.win.print_control_identifiers()
except Exception as e: #捕获所有异常,将异常值给e
self.logger.error(f"应⽤程序启动失败:{e}")
#关闭QQmusic程序
def close(self):
if self.win: #判断self.win是否有值,有值代表窗口定位成功,必满空对象调用close方法报错
self.win.close()
@pytest.fixture(scope="session")#定义夹具,整个测试会话只执行一次,启动一次qq音乐,所有测试用例共用这个实例,测试结束关闭
def QQMusic_app(): #定义夹具函数
QQmusic = QQmusicApp()
QQmusic.launch()
yield QQmusic #先执行yield之前的代码(启动程序),然后将实例返回给测试用例使用,用例执行完之后再执行yield后面的代码
QQmusic.close()
1)所有测试用例共用一个同一个实例的原因:
因为你设置了 scope="session",整个测试会话(比如你运行 pytest test_qqmusic.py 这一次命令)中,只会创建唯一一个 QQmusicApp 实例 → 只启动一次 QQ 音乐 → 所有测试用例都用这个已经启动的 QQ 音乐实例执行操作 → 所有测试完成后,才关闭这一个实例。
2)怎么让这些测试用例都共用同一个实例?
只需在测试用例函数的参数中传入fixture函数名--QQMusic_app,pytest会自动把那个唯一的实例传给测试用例,所有测试用例都是同一个对象。
这样可以提升测试效率,更符合真实使用场景。
logUtils.py
1、【封装日志】
python
import logging
import os.path
import time
class InfoFilter(logging.Filter):#继承自 logging.Filter,用于筛选仅 INFO 级别的日志(不理解)
def filter(self, record):#重写函数
return record.levelno == logging.INFO #仅当日志级别为 INFO 时,返回 True(该日志会被处理);否则返回 False(日志被过滤掉)
class ErrFileter(logging.Filter):
def filter(self, record):
return record.levelno == logging.ERROR #仅当日志级别为 ERROR 时,保留该日志
class Logger:
logger = None #用于存储唯一的日志实例(实现单例的核心) 为什么这样可以实现单例
@classmethod #通过 @classmethod 修饰),无需创建 Logger 实例,直接通过 Logger.getlog() 调用
def getlog(cls): #cls 代表类本身(这里即 Logger 类),用于访问类变量 cls.logger
#创建⽇志对象
if cls.logger is None: #如果 cls.logger 为 None(还未创建日志实例),才执行后续配置;否则直接返回已有的实例(避免重复创建)
cls.logger = logging.getLogger(__name__) #__name__ 是当前模块名,保证日志器的唯一性(避免和其他模块的日志器冲突)
#设置⽇志级别
# DEBUG < INFO < WARNING < ERROR < CRITICAL
cls.logger.setLevel(logging.DEBUG) #所有级别 ≥ DEBUG 的日志都会被处理
LOG_PATH = "logs/"
if not os.path.exists(LOG_PATH):
os.mkdir(LOG_PATH)
#2025-06-30.log 2025-06-30_err.log 2025-06-30_info.log
now = time.strftime("%Y-%m-%d") #获取当前日期
logname = LOG_PATH + now + ".log" #存储所有级别日志
info_logname = LOG_PATH + now + "_info.log" #仅存储 INFO 级别日志的文件
err_logname = LOG_PATH + now + "_err.log" #仅存储 ERROR 级别日志的文件
#创建总⽇志⽂件处理器
handler = logging.FileHandler(logname,encoding="utf-8")#FileHandler将日志写入指定文件
#创建info⽇志⽂件处理器
info_handler = logging.FileHandler(info_logname,encoding="utf-8")
#添加⽂件过滤--该处理器只会写入 INFO 级别日志
info_handler.addFilter(InfoFilter())
#创建err⽇志⽂件处理器
err_handler = logging.FileHandler(err_logname,encoding="utf-8")
err_handler.addFilter(ErrFileter())
#设置⽇志格式
formatter = logging.Formatter(
"%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%
(funcName)s:%(lineno)d)] - %(message)s")
#给三个都设置相同的格式
handler.setFormatter(formatter)
info_handler.setFormatter(formatter)
err_handler.setFormatter(formatter)
#给logger对象添加handler
cls.logger.addHandler(handler)
cls.logger.addHandler(info_handler)
cls.logger.addHandler(err_handler)
return cls.logger
如果不封装日志配置,每次使用日志都要写 logging.getLogger()、设置级别、创建处理器等代码,封装后通过 cls.logger is None 保证整个程序只有一个日志实例,所有模块共用同一套配置,避免冲突
2、【封装yaml】
python
'''
yaml相关的操作
'''
import os
import yaml
def read_yaml(key): #根据这个key读取yaml文件中对应的值
# 拼接YAML文件路径并打开文件
with open(os.getcwd() + "/data/elements.yml", mode="r", encoding="utf-8") as f:
#os.getcwd()获取当前工作目录, 然后拼接文件路径 文件打开模式为只读 将打开的文件对象赋值给变量f,后续通过f操作文件
# 加载YAML文件内容为Python字典
data = yaml.safe_load(f) #将yaml格式的文版转化为python字典
# 根据传入的key返回对应的值
return data[key]
这段代码的作用是 "从 YAML 配置文件中取数据",而 YAML 的作用是 "把需要频繁修改的配置数据单独存起来,让代码更简洁、易维护。
YAML配置文件:主要可变的静态数据,比如自动化测试的控件定位、测试用例数据、环境数据等
核心价值:改配置不用改代码,降低维护成本,让程序更灵活。
test_conmmon.py
1、【测试公共模块】
python
import math
import time
import pytest
from pywinauto import mouse
from Utils.yamlUtils import read_yaml
from Utils.logUtils import Logger
@pytest.mark.order(1) #标记该测试类的执行顺序为第1(需安装pytest-ordering插件)
class TestCommon:
logger = Logger.getlog() #创建日志实例,用于记录测试过程中的信息/错误
'''测试logo'''
def test_logo(self, QQMusic_app):
# 从YAML读取logo控件的定位信息
logo_ele = read_yaml("logo")
# 定位logo控件(通过auto_id和control_type)
logo = QQMusic_app.win.child_window(
auto_id=logo_ele['auto_id'],
control_type=logo_ele["control_type"]
)
# 等待logo控件可见(确保加载完成)
logo.wait("visible")
'''测试------搜索功能'''
def test_search(self, QQMusic_app):
# 从YAML读取搜索框控件定位信息
edit_ele = read_yaml("search")
# 定位搜索框控件
edit = QQMusic_app.win.child_window(
auto_id=edit_ele['auto_id'],
control_type=edit_ele["control_type"]
)
# 点击搜索框,激活输入状态
edit.click_input()
# 先全选(ctrl+a)再输入"邓紫棋",避免追加输入
edit.type_keys("^a邓紫棋")
'''测试------换⽪肤'''
def test_skin(self, QQMusic_app):
# 从YAML读取换肤按钮定位信息
skin_ele = read_yaml("换肤")
# 定位换肤按钮
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):
# 从YAML读取最小化按钮定位信息
window_min_ele = read_yaml("最⼩化")
# 定位最小化按钮
window_min_btn = QQMusic_app.win.child_window(
auto_id=window_min_ele['auto_id'],
control_type=window_min_ele['control_type']
)
# 点击最小化按钮
window_min_btn.click_input()
# 断言窗口是否已最小化
assert QQMusic_app.win.is_minimized()
# 还原窗口(方便后续测试)
QQMusic_app.win.restore()
'''测试------导⼊⾳乐'''
def test_importMusic(self, QQMusic_app):
# 从YAML读取导入音乐按钮定位信息
import_ele = read_yaml("导⼊⾳乐")
# 定位导入音乐按钮
import_btn = QQMusic_app.win.child_window(
auto_id=import_ele['auto_id'],
control_type=import_ele['control_type']
)
# 点击导入音乐按钮
import_btn.click_input()
# 定位"添加本地下载音乐"窗口
import_win = QQMusic_app.win.child_window(
title="添加本地下载⾳乐",
control_type="Window"
)
# 等待窗口可见
import_win.wait("visible")
# 定位音乐列表控件(项目视图)
music_list = import_win.child_window(title="项⽬视图", control_type="List")
# 全选列表(ctrl+a)并按回车确认添加
music_list.type_keys("^a{ENTER}")
# 等待导入窗口关闭(验证添加操作完成)
import_win.wait_not("visible")
'''播放控制模块------随机播放'''
def test_play_random(self, QQMusic_app):
# 从YAML读取"本地下载-播放全部"控件信息
local_ele = read_yaml("本地下载")
play_all_ele = local_ele["播放全部"]
# 定位播放全部按钮
play_btn = QQMusic_app.win.child_window(
auto_id=play_all_ele['auto_id'],
control_type=play_all_ele['control_type']
)
# 循环3次验证随机播放逻辑
for i in range(1, 4):
# 点击播放全部(从第一首"2002年的第一场雪"开始)
play_btn.click_input()
# 定位播放进度条控件
play_ele = read_yaml("播放控制")
process_line_ele = play_ele["播放总进度"]
process_line = QQMusic_app.win.child_window(
auto_id=process_line_ele['auto_id'],
control_type=process_line_ele['control_type']
)
# 获取进度条的矩形区域(用于计算点击坐标)
rec = process_line.rectangle()
x = rec.right - 3 # 进度条尾部x坐标(减3避免超出边界)
y = math.floor((rec.top + rec.bottom)/2) # 进度条垂直中点y坐标
# 鼠标点击进度条尾部,跳转到歌曲末尾
mouse.click(coords=(x, y))
# 等待2秒,让歌曲切换到下一曲
time.sleep(2)
# 定位歌曲名控件,获取当前播放的歌曲名
music_name_ele = play_ele["歌曲名"]
music_name = QQMusic_app.win.child_window(
auto_id=music_name_ele['auto_id'],
control_type=music_name_ele['control_type']
).window_text()
# 校验随机播放逻辑:若下一曲不是"Andy阿杜"(第二首),则随机播放正确
if music_name != "Andy阿杜":
self.logger.info(f"第{i}次判断随机播放下⼀曲正确")
return # 验证通过,退出函数
else:
self.logger.info(f"第{i}次判断随机播放下⼀曲错误")
# 若3次都播放"Andy阿杜",抛出异常(随机播放失效)
raise Exception("随机播放下⼀曲三次判断均错误")
'''播放控制模块------单曲循环'''
def test_play_single(self, QQMusic_app):
# 定位"播放全部"按钮(同随机播放)
local_ele = read_yaml("本地下载")
play_all_ele = local_ele["播放全部"]
play_btn = QQMusic_app.win.child_window(
auto_id=play_all_ele['auto_id'],
control_type=play_all_ele['control_type']
)
# 定位播放模式切换按钮(从随机播放切到单曲循环)
play_ele = read_yaml("播放控制")
playMode_ele = play_ele["模式切换"]
playMode_btn = QQMusic_app.win.child_window(
auto_id=playMode_ele['auto_id'],
control_type=playMode_ele['control_type']
)
# 点击切换模式按钮
playMode_btn.click_input()
# 循环3次验证单曲循环逻辑
for i in range(1, 4):
# 点击播放全部
play_btn.click_input()
# 获取切换前的歌曲名
music_name_ele = play_ele["歌曲名"]
music_name_before = QQMusic_app.win.child_window(
auto_id=music_name_ele['auto_id'],
control_type=music_name_ele['control_type']
).window_text()
# 跳转到歌曲末尾(同随机播放)
process_line_ele = play_ele["播放总进度"]
process_line = QQMusic_app.win.child_window(
auto_id=process_line_ele['auto_id'],
control_type=process_line_ele['control_type']
)
rec = process_line.rectangle()
x = rec.right - 3
y = math.floor((rec.top + rec.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"单曲循环模式播放下⼀⾸歌曲校验错误,before:{music_name_before},after:{music_name_after}")
break # 验证失败,退出循环
else:
self.logger.info(f"第{i}次校验单曲循环模式播放下⼀⾸歌曲正确")
if i == 3:
return # 3次都正确,验证通过
# 若循环中break(验证失败),抛出异常
raise Exception(f"单曲循环模式播放下⼀⾸歌曲校验错误,before:{music_name_before},after:{music_name_after}")
'''播放控制模块------列表循环'''
def test_play_circle(self, QQMusic_app):
# 定位播放模式切换按钮(从单曲循环切到列表循环)
play_ele = read_yaml("播放控制")
music_name_ele = play_ele["歌曲名"]
playMode_ele = play_ele["模式切换"]
playMode_btn = QQMusic_app.win.child_window(
auto_id=playMode_ele['auto_id'],
control_type=playMode_ele['control_type']
)
# 点击切换模式按钮
playMode_btn.click_input()
# 循环3次验证列表循环逻辑
for i in range(1, 4):
# 定位歌曲列表控件
music_list_ele = read_yaml("歌曲列表")
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)
# 获取列表总歌曲数,定位最后一首(索引从0开始,故-1)
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))
# 跳转到歌曲末尾
process_line_ele = play_ele["播放总进度"]
process_line = QQMusic_app.win.child_window(
auto_id=process_line_ele['auto_id'],
control_type=process_line_ele['control_type']
)
rec = process_line.rectangle()
x = rec.right - 3
y = math.floor((rec.top + rec.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()
# 校验列表循环:最后一首播放完应切回第一首"2002年的第一场雪"
if music_name != "2002年的第⼀场雪":
self.logger.error(f"列表循环下⼀曲错误,music_name:{music_name}")
break # 验证失败
else:
self.logger.info(f"第{i}次校验列表循环下⼀曲正确")
if i == 3:
return # 3次都正确
# 验证失败,抛出异常
raise Exception(f"列表循环下⼀曲错误,music_name:{music_name}")
定位控件通过YAML配置(解耦代码和定位信息)
坐标操作(进度条、滚动列表)通过rectangle()精准计算
2、【测试本地下载】
python
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):
# 从YAML读取本地下载模块的控件定位信息
local_ele = read_yaml("本地下载")
# 定位并点击"本地下载"导航栏,进入对应页面
local = QQMusic_app.win.child_window(
auto_id=local_ele["auto_id"],
control_type=local_ele["control_type"]
)
local.click_input()
# 校验"本地音乐"文本
local_text_ele = local_ele["本地⾳乐⽂本"]
local_text = QQMusic_app.win.child_window(
auto_id=local_text_ele["auto_id"],
control_type=local_text_ele["control_type"]
).window_text()
assert local_text == "本地⾳乐"
# 校验"歌曲名称"文本
songname_text_ele = local_ele["歌曲名称⽂本"]
songname_text = QQMusic_app.win.child_window(
auto_id=songname_text_ele["auto_id"],
control_type=songname_text_ele["control_type"]
).window_text()
assert songname_text == "歌曲名称"
# 校验"歌手名称"文本
singername_text_ele = local_ele["歌⼿名称⽂本"]
singername_text = QQMusic_app.win.child_window(
auto_id=singername_text_ele["auto_id"],
control_type=singername_text_ele["control_type"]
).window_text()
assert singername_text == "歌⼿名称"
# 校验"专辑名称"文本(修正原代码变量名笔误:Albumrname→Albumname)
Albumname_text_ele = local_ele["专辑名称⽂本"]
Albumname_text = QQMusic_app.win.child_window(
auto_id=Albumname_text_ele["auto_id"],
control_type=Albumname_text_ele["control_type"]
).window_text()
assert Albumname_text == "专辑名称"
'''测试本地下载模块------播放全部功能'''
def test_local_playAll(self, QQMusic_app):
# 从YAML读取"播放全部"按钮定位信息
local_ele = read_yaml("本地下载")
playAll_ele = local_ele["播放全部"]
playAll_btn = QQMusic_app.win.child_window(
auto_id=playAll_ele["auto_id"],
control_type=playAll_ele["control_type"]
)
# 点击"播放全部"按钮
playAll_btn.click_input()
# 获取点击前的播放进度条右边界坐标(代表当前播放进度)
process_line_ele = read_yaml("播放控制")["当前播放进度"]
process_line_before = QQMusic_app.win.child_window(
auto_id=process_line_ele["auto_id"],
control_type=process_line_ele["control_type"]
)
process_line_len_before = process_line_before.rectangle().right
# 等待2秒,让歌曲播放一段时间
time.sleep(2)
# 获取点击后的播放进度条右边界坐标
process_line_after = QQMusic_app.win.child_window(
auto_id=process_line_ele["auto_id"],
control_type=process_line_ele["control_type"]
)
process_line_len_after = process_line_after.rectangle().right
# 断言:播放进度有变化(证明歌曲在播放)
assert process_line_len_before != process_line_len_after
'''测试本地下载模块------选择歌曲并双击播放'''
def test_local_playSingle(self, QQMusic_app):
# 定位歌曲列表控件
music_list_ele = read_yaml("歌曲列表")
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))
# 获取播放前进度条右边界坐标
process_line_ele = read_yaml("播放控制")["当前播放进度"]
process_line_before = QQMusic_app.win.child_window(
auto_id=process_line_ele["auto_id"],
control_type=process_line_ele["control_type"]
)
process_line_len_before = process_line_before.rectangle().right
# 等待2秒
time.sleep(2)
# 获取播放后进度条右边界坐标
process_line_after = QQMusic_app.win.child_window(
auto_id=process_line_ele["auto_id"],
control_type=process_line_ele["control_type"]
)
process_line_len_after = process_line_after.rectangle().right
# 断言:播放进度有变化(证明单曲播放成功)
assert process_line_len_before != process_line_len_after
'''将歌曲标记喜欢------为后续"我喜欢"模块测试提供数据'''
def test_mark_like(self, QQMusic_app):
# 定位歌曲列表控件,获取列表总歌曲数
music_list_ele = read_yaml("歌曲列表")
music_list = QQMusic_app.win.child_window(
auto_id=music_list_ele["auto_id"],
control_type=music_list_ele["control_type"]
)
list_size = music_list.item_count()
# 遍历所有歌曲,标记为喜欢
for i in range(0, list_size):
# 每6首歌曲向下滚动一次(确保第6首及以后的歌曲可见)
if i != 0 and i % 6 == 0:
point = music_list.rectangle().mid_point()
mouse.scroll(coords=(point.x, point.y), wheel_dist=-500)
# 获取第i首歌曲的矩形区域(用于计算爱心坐标)
rec = music_list.get_item(row=i).rectangle()
# 计算爱心图标的中点坐标(x=歌曲左侧+22像素,y=歌曲行垂直中点)
y = math.floor((rec.top + rec.bottom)/2)
x = rec.left + 22
# 点击爱心图标,标记为喜欢
mouse.click(coords=(x, y))
elements.yml
【数据整理】
python
#根据元素进⾏实时调整
logo:
auto_id: QQMusic.background.head.headLeft.logo
control_type: Text
search:
auto_id: QQMusic.background.head.headRight.searchBox.lineEdit
control_type: Edit
换肤:
auto_id: QQMusic.background.head.headRight.settingBox.skin
control_type: Button
最⼩化:
auto_id: QQMusic.background.head.headRight.settingBox.min
control_type: Button
导⼊⾳乐:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.addLocal
control_type: Button
本地下载:
auto_id: QQMusic.background.body.bodyLeft.leftBox.myMusic.local.btStyle
control_type: Group
播放全部:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.musicPlayBox.playAll.
playAllBtn
control_type: Button
本地⾳乐⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.PageTittle
control_type: Text
歌曲名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicNam
eLabel
control_type: Text
歌⼿名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicSin
gerLabel
control_type: Text
专辑名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.listLabelBox.musicAlb
umLabel
control_type: Text
播放控制:
播放总进度:
auto_id: QQMusic.background.body.bodyRight.progressBar.inLine
control_type: Custom
当前播放进度:
auto_id: QQMusic.background.body.bodyRight.progressBar.outLine
control_type: Custom
歌曲名:
auto_id: QQMusic.background.body.bodyRight.controlBox.play1.musicName
control_type: Text
歌⼿名:
auto_id: QQMusic.background.body.bodyRight.controlBox.play1.musicSinger
control_type: Text
模式切换:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.playMode
control_type: Button
播放:
auto_id: QQMusic.background.body.bodyRight.controlBox.play2.play
control_type: Button
歌曲列表:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.localPage.pageMusicList
control_type: List
推荐:
auto_id: QQMusic.background.body.bodyLeft.leftBox.onlineMusic.rec.btStyle
control_type: Group
推荐⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.recText
control_type: Text
今⽇为你推荐⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.recMusicText
control_type: Text
你的⾳乐补给⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.supplyMusicText
control_type: Text
今⽇为你推荐左滚动:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.recMusicBox.leftPage
control_type: Group
今⽇为你推荐右滚动:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.recMusicBox.rightPage
control_type: Group
今⽇为你推荐第⼀项⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.recMusicBox.musicContent.recListUp.RecBox
Item.recBoxItemText
control_type: Text
⾳乐补给左滚动:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.supplyMuscBox.leftPage
control_type: Group
⾳乐补给右滚动:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.supplyMuscBox.rightPage
control_type: Group
⾳乐补给第⼀排第⼀项⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.supplyMuscBox.musicContent.recListUp.RecB
oxItem.recBoxItemText
control_type: Text
⾳乐补给第⼆排第⼀项⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.recPage.scrollArea.qt_scrollare
a_viewport.scrollAreaWidgetContents_2.supplyMuscBox.musicContent.recListDown.Re
cBoxItem.recBoxItemText
control_type: Text
推荐整个模块:
auto_id: QQMusic.background.body.bodyRight.stackedWidget
control_type: Custom
我喜欢:
auto_id: QQMusic.background.body.bodyLeft.leftBox.myMusic.like.btStyle
control_type: Group
我喜欢⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.PageTittle
control_type: Text
歌曲名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicName
Label
control_type: Text
歌⼿名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicSing
erLabel
control_type: Text
专辑名称⽂本:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.listLabelBox.musicAlbu
mLabel
control_type: Text
播放全部:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.musicPlayBox.playAll.p
layAllBtn
control_type: Button
歌曲列表:
auto_id:
QQMusic.background.body.bodyRight.stackedWidget.likePage.pageMusicList
control_type: List
歌词⼊⼝:
auto_id: QQMusic.background.body.bodyRight.controlBox.play3.lrcWord
control_type: Button
歌⼿标题⽂本:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.titleBox.musicSinger
control_type: Text
歌曲名标题⽂本:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.titleBox.musicName
control_type: Text
收起歌词:
auto_id: QQMusic.LrcPage.bgStyle.lrcTop.hideBtn
control_type: Button
歌词列表:
auto_id: QQMusic.LrcPage.bgStyle.lrcContent
control_type: Group
pytest.ini
【pytest配置】
python
[pytest]
#每次运行pytest时,自动追加这些参数,无需手动输入
addopts = -vs -p no:faulthandler --alluredir=./reports/source --clean-alluredir
#指定pytest的测试用例根目录
testpaths=tests
; python_files=test_*.py #测试文件必须以test_开头
; python_classes=Test* #测试类必须以Test开头
; python_functions=test_* #测试方法必须以test_开头
生成测试报告
allure generate .\report\allure_results\ -o .\report\html\ --clean
使用cursor赋能GUI自动化测试
使用windows-mcp实现零代码自动化测试
安装window-mcp:
1)在cmd安装:
pip install uv
2)下载window-mcp源码:
git clone https://github.com/CursorTouch/Windows-MCP.git
3)在cursor中添加mcp配置:
在当前的应⽤实践中,Windows-MCP 虽然具备零代码⾃动化的能⼒,为测试⾃动化提供了新的思路和便利性,但在实际落地过程中仍存在⼀定的局限性。首先,即便通过⼿动编写⾃动化脚本,系统稳定性仍难以完全保障。其次,从 Windows-MCP 的执⾏结果来看,其在稳定性⽅⾯的表现更为不足。由此可见,将 Windows-MCP 应用于项⽬级⾃动化测试的实战中,仍需持续探索和优化,其在⼯程化与大规模应⽤层⾯尚有较长的提升空间。


