【GUI自动化测试】--基于QQ音乐项目的GUI自动化测试

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 应用于项⽬级⾃动化测试的实战中,仍需持续探索和优化,其在⼯程化与大规模应⽤层⾯尚有较长的提升空间。

相关推荐
楚轩努力变强1 个月前
iOS 自动化环境配置指南 (Appium + WebDriverAgent)
javascript·学习·macos·ios·appium·自动化
ELI_He9991 个月前
appium-doctor optional dependencies
appium
测试人社区—66792 个月前
2025区块链分层防御指南:AI驱动的安全测试实战策略
开发语言·驱动开发·python·appium·pytest
测试19982 个月前
如何使用Appium实现移动端UI自动化测试?
自动化测试·软件测试·python·测试工具·ui·appium·测试用例
HEADKON2 个月前
米托坦Mitotane长期治疗中的疗效监测与基于毒性的个体化剂量调整
appium
shughui2 个月前
Android SDK 下载、安装与配置(详细图文附安装包,适配Appium+Python自动化)
android·python·appium·android-studio·android sdk
未定义.2212 个月前
第7篇:跨端拓展!Playwright+Appium实现Web+移动端全覆盖
python·ui·appium·自动化·jenkins·pytest
BullSmall2 个月前
自动化测试开发规范
selenium·测试工具·appium·自动化
BatyTao2 个月前
Appium-Inspector下载安装全攻略
appium