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

相关推荐
returnthem5 天前
安装Appium
appium
seabirdssss10 天前
Appium 在小米平板上的安装受限与闪退排查
android·appium·电脑
小陈的进阶之路14 天前
Selenium 滑动 vs Appium 滑动
python·selenium·测试工具·appium
小陈的进阶之路14 天前
Appium 自动化测试笔记
笔记·appium
linglan42816 天前
APPium环境配置
appium·自动化
lifewange21 天前
Appium是什么
appium·压力测试
柚子+1 个月前
Appium+python+雷电模拟器自动化测试入门
数据库·python·appium
楚轩努力变强3 个月前
iOS 自动化环境配置指南 (Appium + WebDriverAgent)
javascript·学习·macos·ios·appium·自动化