【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

目录

前言

一、项目背景与测试规划:先明确 "测什么" 和 "怎么测"

1.1 项目介绍

1.2 测试目标

1.3 测试范围与用例设计

​编辑

二、环境搭建:3 步搞定自动化测试前置准备

2.1 安装核心依赖包

2.2 浏览器配置

2.3 项目目录结构设计

三、核心模块开发:封装公共工具,提高代码复用性

3.1 驱动管理与截图工具封装(common/Utils.py)

3.2 代码说明与优化点

四、测试用例开发:逐个模块实现自动化测试

4.1 登录模块测试(cases/BlogLogin.py)

4.2 博客列表模块测试(cases/BlogList.py)

4.3 博客详情模块测试(cases/BlogDetail.py)

4.4 博客编辑模块测试(cases/BlogEdit.py)

五、测试用例执行入口:一键运行所有测试(RunCases.py)

六、测试报告生成:打造专业、清晰的测试成果文档

【测试报告】博客系统 UI 自动化测试报告

1. 报告基本信息

2. 项目背景

2.1 测试目标及测试任务概括

2.2 被测系统及相关信息

2.3 产品需求和设计文档

3. 测试安排

4. 测试结果汇总

4.1 测试用例执行情况

4.2 缺陷统计与分析

4.3 截图示例

5. 测试结论与建议

5.1 测试结论

5.2 后续建议

总结


前言

        在如今的软件开发流程中,"测试" 不再是测试工程师的专属工作。尤其是对于 C++ 方向的 Web 项目开发,重复的手动测试不仅耗时耗力,还容易出现遗漏和误判。想象一下:每次迭代都要反复验证登录功能、博客列表展示、编辑提交等核心流程,稍有疏忽就可能让 bug 流入生产环境。

        而自动化测试的出现,正是为了解决这个痛点 —— 它就像一个不知疲倦的 "测试助手",能自动执行预设用例、精准捕获异常、生成测试报告,让开发者把更多精力放在核心功能开发上。本文将以经典的博客系统为案例,手把手教你用 Python 搭建一套完整的 UI 自动化测试框架,从用例设计到脚本开发,再到测试报告落地,全程实战、代码可直接复用,适合 C++ 开发者、测试工程师及所有想要入门自动化测试的小伙伴。下面就让我们正式开始吧!

一、项目背景与测试规划:先明确 "测什么" 和 "怎么测"

1.1 项目介绍

        本次实战的被测对象是一个基于 Web 的博客系统(后端 C++ 开发),核心功能模块包括:登录模块、博主信息模块、博客列表模块、博客编辑模块、博客详情模块。系统支持用户登录后查看、编辑、提交博客,未登录用户访问受限功能时会触发登录弹窗提示。

1.2 测试目标

验证博客系统核心功能的稳定性和正确性;实现关键流程的自动化执行,减少手动测试成本;快速定位迭代过程中引入的回归 bug;输出清晰、规范的测试报告,支撑项目上线决策。

1.3 测试范围与用例设计

        自动化测试并非 "面面俱到",而是聚焦核心流程和高频场景。结合系统功能,我们设计了以下测试用例(覆盖正常场景与异常场景):

二、环境搭建:3 步搞定自动化测试前置准备

        在开始编码前,我们需要先完成环境配置。请确保你的电脑已安装 Python 环境(3.8 及以上版本),然后按照以下步骤操作:

2.1 安装核心依赖包

        打开命令行终端,执行以下命令安装所需库:

# 安装Selenium(UI自动化核心库) pip install selenium==4.10.0 # 安装webdriver-manager(自动管理浏览器驱动) pip install webdriver-manager==4.0.0 

2.2 浏览器配置

        本文以 Chrome 浏览器为例(推荐版本 110+),无需手动下载 ChromeDriver:webdriver-manager 会自动检测浏览器版本并下载对应驱动,彻底解决 "驱动版本不匹配" 的问题。

        如果需要使用 Firefox 或 Edge 浏览器,只需修改后续代码中的浏览器配置(下文会详细说明)。

2.3 项目目录结构设计

        一个清晰的目录结构是自动化框架可维护的关键。我们采用以下目录结构组织项目:

blog_auto_test/ # 项目根目录 ├── common/ # 公共工具模块 │ └── Utils.py # 驱动管理、截图等公共功能 ├── cases/ # 测试用例模块 │ ├── BlogLogin.py # 登录模块测试用例 │ ├── BlogList.py # 博客列表模块测试用例 │ ├── BlogEdit.py # 博客编辑模块测试用例(后续补充) │ └── BlogDetail.py # 博客详情模块测试用例 ├── images/ # 截图存储目录(自动生成) │ └── 2024-05-20/ # 按日期分类的截图文件夹 ├── reports/ # 测试报告目录 │ └── test_report.md # 测试报告文件 └── RunCases.py # 测试用例执行入口

三、核心模块开发:封装公共工具,提高代码复用性

        在编写具体测试用例前,我们先封装公共工具类 —— 这能避免重复编码,让后续用例开发更高效。核心公共功能包括:驱动对象创建、自动截图、异常处理等。

3.1 驱动管理与截图工具封装(common/Utils.py)

import datetime import os.path import sys from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.common.exceptions import WebDriverException class Driver: """驱动管理类:创建浏览器驱动、截图、关闭驱动等功能封装""" driver = None # 类属性,全局共享驱动对象 def __init__(self): """初始化驱动对象,配置Chrome浏览器选项""" try: # 创建Chrome浏览器选项对象 options = webdriver.ChromeOptions() # 可选配置:无头模式(无界面运行,适合服务器环境) # options.add_argument('--headless=new') # 可选配置:忽略证书错误 options.add_argument('--ignore-certificate-errors') # 可选配置:禁用GPU加速(避免部分环境报错) options.add_argument('--disable-gpu') # 可选配置:设置窗口大小 options.add_argument('window-size=1920,1080') # 自动安装匹配的ChromeDriver并创建驱动对象 self.driver = webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options ) # 设置页面加载超时时间(10秒) self.driver.set_page_load_timeout(10) # 设置隐式等待时间(5秒,等待元素加载) self.driver.implicitly_wait(5) print(f"驱动初始化成功!ChromeDriver版本:{self.driver.capabilities['chrome']['chromedriverVersion']}") except WebDriverException as e: print(f"驱动初始化失败:{str(e)}") raise # 抛出异常,终止程序运行 except Exception as e: print(f"未知错误:{str(e)}") raise def get_screen_shot(self): """ 截图功能:按日期创建文件夹,截图文件名包含用例名和时间戳 返回值:截图文件路径 """ try: # 按当前日期创建截图文件夹(如:images/2024-05-20) dirname = datetime.datetime.now().strftime('%Y-%m-%d') screenshot_dir = f"../images/{dirname}" # 截图存储路径 if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) print(f"创建截图文件夹:{screenshot_dir}") # 获取调用该方法的函数名(即测试用例名) case_name = sys._getframe().f_back.f_code.co_name # 截图文件名:用例名-时间戳.png(如:loginSucTest-2024-05-20-143025.png) filename = f"{case_name}-{datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S')}.png" screenshot_path = os.path.join(screenshot_dir, filename) # 执行截图并保存 self.driver.save_screenshot(screenshot_path) print(f"截图成功!路径:{screenshot_path}") return screenshot_path except Exception as e: print(f"截图失败:{str(e)}") return None def quit_driver(self): """关闭驱动,释放资源""" if self.driver: self.driver.quit() print("🔌 驱动已关闭,资源释放完成") # 创建全局驱动实例(所有用例共享同一个驱动) blog_driver = Driver() 

3.2 代码说明与优化点

驱动全局共享:通过类属性driver和全局实例blog_driver,确保所有测试用例使用同一个浏览器实例,避免重复打开浏览器,提高执行效率;异常处理:捕获WebDriverException等常见异常,并打印详细日志,方便问题定位;灵活配置:预留了无头模式、窗口大小等可选配置,可根据实际测试环境调整;截图优化:按日期分类存储截图,文件名包含用例名和时间戳,便于追溯测试场景。

四、测试用例开发:逐个模块实现自动化测试

        接下来,我们按照测试规划,逐个模块编写测试用例。每个用例都遵循 "前置条件→操作步骤→断言验证" 的逻辑,确保测试结果的准确性。

4.1 登录模块测试(cases/BlogLogin.py)

        登录模块是系统的入口,也是自动化测试的核心场景。我们需要覆盖正常登录和多种异常登录场景:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver class BlogLogin: """登录模块测试用例""" def __init__(self): """初始化:设置登录页面URL,打开登录页""" self.login_url = "http://192.168.47.135:8653/blog_system/blog_login.html" self.driver = blog_driver.driver # 获取全局驱动对象 self.driver.get(self.login_url) print(f"打开登录页面:{self.login_url}") def clear_input(self): """清空账号和密码输入框(复用方法)""" username_input = self.driver.find_element(By.CSS_SELECTOR, "#username") password_input = self.driver.find_element(By.CSS_SELECTOR, "#password") username_input.clear() password_input.clear() print("清空账号密码输入框") def login_suc_test(self, username="admin", password="123"): """ 正常登录测试用例 :param username: 测试账号 :param password: 测试密码 """ try: print(f"\n===== 执行正常登录测试(账号:{username},密码:{password})=====") # 清空输入框 self.clear_input() # 输入账号密码 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username) self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password) print(f"输入账号:{username},密码:{password}") # 点击登录按钮 self.driver.find_element(By.CSS_SELECTOR, "#submit").click() print("点击登录按钮") # 等待页面跳转(避免页面未加载完成就断言) time.sleep(2) # 断言:登录成功后跳转到博客列表页,且能找到博主头像元素 try: # 博主头像元素选择器(根据实际页面调整) avatar_element = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > img") assert avatar_element.is_displayed() # 验证元素是否可见 print(f"正常登录测试通过!{username}账号登录成功") # 截图记录成功场景 blog_driver.get_screen_shot() # 退回登录页,为后续测试做准备 self.driver.back() time.sleep(1) except NoSuchElementException: print(f"正常登录测试失败:未找到博主头像元素,可能未跳转至列表页") blog_driver.get_screen_shot() raise except AssertionError: print(f"正常登录测试失败:博主头像元素不可见") blog_driver.get_screen_shot() raise except Exception as e: print(f"正常登录测试异常:{str(e)}") blog_driver.get_screen_shot() raise def login_fail_test(self, username="admin", password="111", expect_msg="用户名或密码错误!"): """ 异常登录测试用例 :param username: 测试账号 :param password: 测试密码 :param expect_msg: 预期错误提示 """ try: print(f"\n===== 执行异常登录测试(账号:{username},密码:{password})=====") # 清空输入框 self.clear_input() # 输入账号密码 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username) self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password) print(f"输入账号:{username},密码:{password}") # 点击登录按钮 self.driver.find_element(By.CSS_SELECTOR, "#submit").click() print("点击登录按钮") # 等待错误提示加载 time.sleep(1) # 断言:页面显示预期的错误提示 actual_msg = self.driver.find_element(By.CSS_SELECTOR, "body").text print(f"预期错误提示:{expect_msg}") print(f"实际错误提示:{actual_msg}") assert expect_msg in actual_msg, f"错误提示不匹配!预期:{expect_msg},实际:{actual_msg}" print(f"异常登录测试通过!错误提示符合预期") # 截图记录失败场景 blog_driver.get_screen_shot() # 退回登录页(若页面未跳转,可省略) self.driver.back() time.sleep(1) except NoSuchElementException: print(f"异常登录测试失败:未找到错误提示元素") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"异常登录测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"异常登录测试异常:{str(e)}") blog_driver.get_screen_shot() raise def login_empty_username_test(self): """账号为空登录测试""" print(f"\n===== 执行账号为空登录测试 =====") self.clear_input() # 仅输入密码 self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys("123") print(f"账号为空,输入密码:123") self.driver.find_element(By.CSS_SELECTOR, "#submit").click() time.sleep(1) # 断言:页面未跳转,且可能有错误提示(根据实际需求调整) assert self.driver.current_url == self.login_url, "账号为空时页面不应跳转" print(f"账号为空登录测试通过!页面未跳转") blog_driver.get_screen_shot() def login_empty_password_test(self): """密码为空登录测试""" print(f"\n===== 执行密码为空登录测试 =====") self.clear_input() # 仅输入账号 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys("admin") print(f"输入账号:admin,密码为空") self.driver.find_element(By.CSS_SELECTOR, "#submit").click() time.sleep(1) # 断言:页面未跳转 assert self.driver.current_url == self.login_url, "密码为空时页面不应跳转" print(f"密码为空登录测试通过!页面未跳转") blog_driver.get_screen_shot() # 测试代码(单独运行时执行) if __name__ == "__main__": login_test = BlogLogin() # 执行正常登录测试(admin账号) login_test.login_suc_test("admin", "123") # 执行正常登录测试(lisi账号) login_test.login_suc_test("lisi", "123") # 执行密码错误测试 login_test.login_fail_test("admin", "111", "用户名或密码错误!") # 执行账号为空测试 login_test.login_empty_username_test() # 执行密码为空测试 login_test.login_empty_password_test() # 关闭驱动 blog_driver.quit_driver() 

4.2 博客列表模块测试(cases/BlogList.py)

        博客列表模块的核心测试点是 "登录状态下正常访问" 和 "未登录状态下跳转登录页",同时需要验证博客数量和跳转功能:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin class BlogList: """博客列表模块测试用例""" def __init__(self): """初始化:设置列表页URL,获取驱动对象""" self.list_url = "http://192.168.47.135:8653/blog_system/blog_list.html" self.driver = blog_driver.driver print(f"博客列表页URL:{self.list_url}") def list_login_status_test(self): """登录状态下访问博客列表页测试""" try: print(f"\n===== 执行登录状态下博客列表测试 =====") # 前置条件:先登录admin账号 login_test = BlogLogin() login_test.login_suc_test("admin", "123") # 打开博客列表页 self.driver.get(self.list_url) print(f"已登录,访问列表页:{self.list_url}") time.sleep(2) # 断言1:页面显示博主头像(验证登录状态有效) avatar_element = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > img") assert avatar_element.is_displayed(), "博主头像未显示,登录状态可能失效" print("断言通过:博主头像正常显示") # 断言2:页面显示第一篇博客链接 first_blog = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > a") assert first_blog.is_displayed(), "第一篇博客链接未找到" print(f"断言通过:第一篇博客链接正常显示(标题:{first_blog.text})") # 断言3:博客数量大于10篇 all_blogs = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div") blog_count = len(all_blogs) print(f"当前博客总数:{blog_count}") assert blog_count > 10, f"博客数量不足10篇,实际数量:{blog_count}" print("断言通过:博客数量大于10篇") # 操作:点击第一篇博客,跳转至详情页 first_blog.click() time.sleep(2) # 断言4:跳转至博客详情页(通过标题验证) assert self.driver.title == "博客详情页", f"跳转失败,当前页面标题:{self.driver.title}" print("断言通过:成功跳转至博客详情页") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下博客列表测试通过 =====") except NoSuchElementException as e: print(f"登录状态下博客列表测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"登录状态下博客列表测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"登录状态下博客列表测试异常:{str(e)}") blog_driver.get_screen_shot() raise def list_unlogin_status_test(self): """未登录状态下访问博客列表页测试""" try: print(f"\n===== 执行未登录状态下博客列表测试 =====") # 前置条件:确保未登录(关闭所有关联页面,重新打开浏览器) blog_driver.quit_driver() # 关闭现有驱动 new_driver = Driver() # 创建新驱动(未登录状态) self.driver = new_driver.driver # 直接访问博客列表页 self.driver.get(self.list_url) print(f"未登录,直接访问列表页:{self.list_url}") time.sleep(2) # 断言:跳转至登录页(通过URL验证) assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下访问列表页,自动跳转至登录页") # 截图记录 new_driver.get_screen_shot() # 关闭新驱动 new_driver.quit_driver() print("===== 未登录状态下博客列表测试通过 =====") except AssertionError as e: print(f"未登录状态下博客列表测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录状态下博客列表测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": list_test = BlogList() # 执行登录状态下测试 list_test.list_login_status_test() # 执行未登录状态下测试 list_test.list_unlogin_status_test() 

4.3 博客详情模块测试(cases/BlogDetail.py)

        博客详情模块依赖博客列表模块的跳转,核心测试点是页面元素完整性和未登录访问限制:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin from cases.BlogList import BlogList class BlogDetail: """博客详情模块测试用例""" def __init__(self, blog_id=15): """初始化:设置详情页URL(带博客ID)""" self.detail_url = f"http://192.168.47.135:8653/blog_system/blog_detail.html?blogId={blog_id}" self.driver = blog_driver.driver self.blog_id = blog_id print(f"博客详情页URL(ID:{blog_id}):{self.detail_url}") def detail_login_status_test(self): """登录状态下访问博客详情页测试""" try: print(f"\n===== 执行登录状态下博客详情测试(ID:{self.blog_id})=====") # 前置条件:登录并进入列表页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") list_test = BlogList() list_test.driver.get(list_test.list_url) time.sleep(2) # 从列表页点击博客进入详情页(模拟用户真实操作) first_blog = list_test.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > a") first_blog.click() time.sleep(2) # 断言1:页面标题为"博客详情页" assert self.driver.title == "博客详情页", f"当前页面不是详情页,标题:{self.driver.title}" print("断言通过:页面标题为'博客详情页'") # 断言2:显示博客标题 blog_title = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div > h3") assert blog_title.is_displayed(), "博客标题未显示" print(f"断言通过:博客标题正常显示(标题:{blog_title.text})") # 断言3:显示发布日期 publish_date = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div > div.date") assert publish_date.is_displayed(), "发布日期未显示" print(f"断言通过:发布日期正常显示(日期:{publish_date.text})") # 断言4:显示博客正文 blog_content = self.driver.find_element(By.CSS_SELECTOR, "#content") assert blog_content.is_displayed(), "博客正文未显示" print(f"断言通过:博客正文正常显示(前50字:{blog_content.text[:50]}...)") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下博客详情测试通过 =====") except NoSuchElementException as e: print(f"登录状态下博客详情测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"登录状态下博客详情测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"登录状态下博客详情测试异常:{str(e)}") blog_driver.get_screen_shot() raise def detail_unlogin_status_test(self): """未登录状态下访问博客详情页测试""" try: print(f"\n===== 执行未登录状态下博客详情测试(ID:{self.blog_id})=====") # 前置条件:未登录(创建新驱动) blog_driver.quit_driver() new_driver = Driver() self.driver = new_driver.driver # 直接访问详情页URL self.driver.get(self.detail_url) print(f"未登录,直接访问详情页:{self.detail_url}") time.sleep(2) # 断言:跳转至登录页 assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下访问详情页,自动跳转至登录页") # 截图记录 new_driver.get_screen_shot() new_driver.quit_driver() print("===== 未登录状态下博客详情测试通过 =====") except AssertionError as e: print(f"未登录状态下博客详情测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录状态下博客详情测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": detail_test = BlogDetail(blog_id=15) # 执行登录状态下测试 detail_test.detail_login_status_test() # 执行未登录状态下测试 detail_test.detail_unlogin_status_test() 

4.4 博客编辑模块测试(cases/BlogEdit.py)

        博客编辑模块是核心功能之一,需要覆盖正常提交和异常提交(无标题、无内容)场景:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin class BlogEdit: """博客编辑模块测试用例""" def __init__(self): """初始化:设置编辑页URL,获取驱动对象""" self.edit_url = "http://192.168.47.135:8653/blog_system/blog_edit.html" self.driver = blog_driver.driver print(f"博客编辑页URL:{self.edit_url}") def edit_login_normal_submit_test(self): """登录状态下正常提交博客测试""" try: print(f"\n===== 执行登录状态下正常提交博客测试 =====") # 前置条件:登录admin账号 login_test = BlogLogin() login_test.login_suc_test("admin", "123") # 打开编辑页 self.driver.get(self.edit_url) time.sleep(2) print(f"已登录,访问编辑页:{self.edit_url}") # 输入博客标题和内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") # 假设标题输入框ID为title content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") # 假设内容输入框ID为content submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") # 假设提交按钮ID为submit blog_title = f"自动化测试实战_测试博客_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}" blog_content = "这是一篇通过自动化测试脚本提交的博客,用于验证编辑模块的正常提交功能。\n测试内容:标题+正文完整填写,提交后应跳转至列表页并显示新增博客。" title_input.clear() title_input.send_keys(blog_title) content_input.clear() content_input.send_keys(blog_content) print(f"输入博客标题:{blog_title}") print(f"输入博客内容:{blog_content[:30]}...") # 点击提交按钮 submit_btn.click() time.sleep(3) # 等待提交完成并跳转 print("点击提交按钮") # 断言1:跳转至博客列表页 assert self.driver.title == "博客列表页", f"提交后未跳转至列表页,当前标题:{self.driver.title}" print("断言通过:提交后成功跳转至列表页") # 断言2:列表页显示新增的博客 all_blogs = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div > a") blog_titles = [blog.text for blog in all_blogs] assert blog_title in blog_titles, f"新增博客未在列表中显示(标题:{blog_title})" print(f"断言通过:新增博客已显示在列表页") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下正常提交博客测试通过 =====") except NoSuchElementException as e: print(f"正常提交博客测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"正常提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"正常提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_login_no_title_submit_test(self): """登录状态下无标题提交测试""" try: print(f"\n===== 执行登录状态下无标题提交博客测试 =====") # 前置条件:登录并打开编辑页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") self.driver.get(self.edit_url) time.sleep(2) # 仅输入内容,不输入标题 content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") blog_content = "这是无标题提交测试的内容,预期提交失败。" content_input.clear() content_input.send_keys(blog_content) print(f"未输入标题,输入内容:{blog_content}") # 点击提交按钮 submit_btn.click() time.sleep(1) print("点击提交按钮") # 断言:页面未跳转,仍停留在编辑页 assert self.driver.current_url == self.edit_url, f"无标题提交时页面不应跳转,当前URL:{self.driver.current_url}" print("断言通过:无标题提交时页面未跳转") # (可选)断言:显示"请输入标题"提示 try: error_msg = self.driver.find_element(By.CSS_SELECTOR, "#title-error").text # 假设错误提示元素 assert "请输入标题" in error_msg, f"错误提示不匹配,实际:{error_msg}" print(f"断言通过:显示正确的错误提示:{error_msg}") except NoSuchElementException: print("未找到错误提示元素,但页面未跳转,测试通过") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下无标题提交博客测试通过 =====") except AssertionError as e: print(f"无标题提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"无标题提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_login_no_content_submit_test(self): """登录状态下无内容提交测试""" try: print(f"\n===== 执行登录状态下无内容提交博客测试 =====") # 前置条件:登录并打开编辑页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") self.driver.get(self.edit_url) time.sleep(2) # 仅输入标题,不输入内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") blog_title = "无内容提交测试的标题" title_input.clear() title_input.send_keys(blog_title) print(f"输入标题:{blog_title},未输入内容") # 点击提交按钮 submit_btn.click() time.sleep(1) print("点击提交按钮") # 断言:页面未跳转 assert self.driver.current_url == self.edit_url, f"无内容提交时页面不应跳转,当前URL:{self.driver.current_url}" print("断言通过:无内容提交时页面未跳转") # (可选)断言:显示"请输入内容"提示 try: error_msg = self.driver.find_element(By.CSS_SELECTOR, "#content-error").text assert "请输入内容" in error_msg, f"错误提示不匹配,实际:{error_msg}" print(f"断言通过:显示正确的错误提示:{error_msg}") except NoSuchElementException: print("未找到错误提示元素,但页面未跳转,测试通过") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下无内容提交博客测试通过 =====") except AssertionError as e: print(f"无内容提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"无内容提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_unlogin_submit_test(self): """未登录状态下提交博客测试""" try: print(f"\n===== 执行未登录状态下提交博客测试 =====") # 前置条件:未登录 blog_driver.quit_driver() new_driver = Driver() self.driver = new_driver.driver # 直接访问编辑页并尝试提交 self.driver.get(self.edit_url) time.sleep(2) print(f"未登录,访问编辑页:{self.edit_url}") # 输入标题和内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") title_input.send_keys("未登录提交测试") content_input.send_keys("未登录状态下不应提交成功") print("输入标题和内容,尝试提交") # 点击提交按钮 submit_btn.click() time.sleep(2) print("点击提交按钮") # 断言:跳转至登录页或提交按钮失效(根据实际逻辑调整) assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未登录提交应跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下提交,跳转至登录页") # 截图记录 new_driver.get_screen_shot() new_driver.quit_driver() print("===== 未登录状态下提交博客测试通过 =====") except AssertionError as e: print(f"未登录提交博客测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录提交博客测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": edit_test = BlogEdit() # 执行正常提交测试 edit_test.edit_login_normal_submit_test() # 执行无标题提交测试 edit_test.edit_login_no_title_submit_test() # 执行无内容提交测试 edit_test.edit_login_no_content_submit_test() # 执行未登录提交测试 edit_test.edit_unlogin_submit_test() 

五、测试用例执行入口:一键运行所有测试(RunCases.py)

        为了方便执行所有测试用例,我们创建一个统一的执行入口文件,按模块顺序执行测试:

import sys import time from common.Utils import blog_driver from cases.BlogLogin import BlogLogin from cases.BlogList import BlogList from cases.BlogDetail import BlogDetail from cases.BlogEdit import BlogEdit def run_all_cases(): """执行所有自动化测试用例""" print("="*50) print("开始执行博客系统自动化测试用例") print(f"执行时间:{time.strftime('%Y-%m-%d %H:%M:%S')}") print("="*50) # 存储测试结果 test_results = { "passed": 0, "failed": 0, "total": 0 } try: # 1. 执行登录模块测试 print("\n" + "="*30) print("开始执行登录模块测试") print("="*30) login_test = BlogLogin() # 正常登录(admin) login_test.login_suc_test("admin", "123") test_results["passed"] += 1 # 正常登录(lisi) login_test.login_suc_test("lisi", "123") test_results["passed"] += 1 # 密码错误 login_test.login_fail_test("admin", "111", "用户名或密码错误!") test_results["passed"] += 1 # 账号为空 login_test.login_empty_username_test() test_results["passed"] += 1 # 密码为空 login_test.login_empty_password_test() test_results["passed"] += 1 # 2. 执行博客列表模块测试 print("\n" + "="*30) print("开始执行博客列表模块测试") print("="*30) list_test = BlogList() # 登录状态下测试 list_test.list_login_status_test() test_results["passed"] += 1 # 未登录状态下测试 list_test.list_unlogin_status_test() test_results["passed"] += 1 # 3. 执行博客详情模块测试 print("\n" + "="*30) print("开始执行博客详情模块测试") print("="*30) detail_test = BlogDetail(blog_id=15) # 登录状态下测试 detail_test.detail_login_status_test() test_results["passed"] += 1 # 未登录状态下测试 detail_test.detail_unlogin_status_test() test_results["passed"] += 1 # 4. 执行博客编辑模块测试 print("\n" + "="*30) print("开始执行博客编辑模块测试") print("="*30) edit_test = BlogEdit() # 正常提交 edit_test.edit_login_normal_submit_test() test_results["passed"] += 1 # 无标题提交 edit_test.edit_login_no_title_submit_test() test_results["passed"] += 1 # 无内容提交 edit_test.edit_login_no_content_submit_test() test_results["passed"] += 1 # 未登录提交 edit_test.edit_unlogin_submit_test() test_results["passed"] += 1 except Exception as e: print(f"\n测试执行过程中出现异常:{str(e)}") test_results["failed"] += 1 finally: # 计算总用例数 test_results["total"] = test_results["passed"] + test_results["failed"] # 关闭驱动 blog_driver.quit_driver() # 输出测试总结 print("\n" + "="*50) print("博客系统自动化测试执行完成") print("="*50) print(f"总用例数:{test_results['total']}") print(f"通过用例数:{test_results['passed']}") print(f"失败用例数:{test_results['failed']}") pass_rate = (test_results['passed'] / test_results['total']) * 100 if test_results['total'] > 0 else 100 print(f"测试通过率:{pass_rate:.2f}%") print("="*50) # 若有失败用例,退出码设为1(便于CI/CD集成) if test_results["failed"] > 0: sys.exit(1) else: sys.exit(0) if __name__ == "__main__": run_all_cases() 

六、测试报告生成:打造专业、清晰的测试成果文档

        测试执行完成后,需要生成一份规范的测试报告,用于向团队展示测试结果、进度和问题。以下为大家提供基于本次实战的测试报告模板(可根据实际项目调整):

【测试报告】博客系统 UI 自动化测试报告

1. 报告基本信息

项目名称博客系统(C++ 后端 Web 项目)版本号V1.0
发布类型分级发布测试负责人OPCHEN
测试完成日期2026-1-14联系方式(根据实际填写)
评审人王一博、肖战、马绵、李产品、王交互批准人李产品
评审日期2026-1-14批准日期2026-1-14

2. 项目背景

2.1 测试目标及测试任务概括
测试目标:验证博客系统核心功能(登录、列表、详情、编辑)的正确性和稳定性,实现关键流程自动化,减少手动测试成本,支撑项目上线;测试任务:设计并执行 14 条 UI 自动化测试用例,覆盖正常场景与异常场景,生成测试报告并跟踪问题修复。
2.2 被测系统及相关信息
被测系统:博客系统(Web 端);系统地址:http://xxxxxxxxxxxxxxxxxxxxxx;代码包及文档:diff 链接(根据实际填写)、接口文档(根据实际填写)、《测试计划》;依赖环境:Chrome 浏览器 110+、Python 3.8+、Selenium 4.10.0。
2.3 产品需求和设计文档
《博客系统需求文档》;《博客系统技术设计文档》;《博客系统 UI 设计图》。

3. 测试安排

模块子模块前端开发后端开发提测时间测试负责人工时排期进度备注
登录模块登录功能肖战王一博2024-05-18OPCHEN0.5d2026-1-10测试完成覆盖正常 / 异常登录
博客列表模块列表展示、跳转肖战王一博2024-05-18OPCHEN0.5d2026-1-10测试完成登录 / 未登录状态
博客详情模块详情展示肖战王一博2024-05-18OPCHEN0.5d2026-1-11测试完成登录 / 未登录状态
博客编辑模块编辑、提交肖战王一博2024-05-18OPCHEN0.5d2026-1-11测试完成正常 / 异常提交

4. 测试结果汇总

4.1 测试用例执行情况
模块用例总数通过数失败数通过率
登录模块550100.00%
博客列表模块220100.00%
博客详情模块220100.00%
博客编辑模块550100.00%
总计14140100.00%
4.2 缺陷统计与分析
本次自动化测试未发现新增缺陷;历史缺陷修复验证:所有已提交的缺陷均已修复,且在本次自动化测试中无回归。
4.3 截图示例
测试场景截图路径说明
admin 账号正常登录成功images/2026-1-10/loginSucTest-20260110143025.png跳转至列表页,显示博主头像
密码错误异常登录images/2026-1-10/loginFailTest-20260110143210.png显示 "用户名或密码错误!"
博客列表页正常展示images/2026-1-11/listLoginStatusTest-20260111143508.png博客数量大于 10 篇
编辑博客正常提交images/2026-1-11/editLoginNormalSubmitTest-20260111144015.png新增博客显示在列表页

5. 测试结论与建议

5.1 测试结论
博客系统核心功能(登录、列表、详情、编辑)的 UI 自动化测试已全部完成,14 条用例全部通过,通过率 100%;系统在测试场景下运行稳定,未出现功能异常、崩溃等问题;自动化测试框架运行正常,能够准确执行测试用例、捕获异常并生成截图,可支撑后续迭代的回归测试。
5.2 后续建议
扩展用例覆盖范围:新增博客删除、修改密码、评论等功能的自动化用例;集成 CI/CD 流程:将自动化测试脚本集成到 Jenkins 等工具,实现每次代码提交后自动执行测试;优化框架稳定性:增加重试机制(如元素未找到时自动重试)、日志详细程度优化;定期维护用例:当系统 UI 发生变更时,及时更新用例中的元素选择器,确保自动化用例的有效性。

总结

        通过本次博客系统的自动化测试实战,我们从 0 到 1 搭建了一套完整的 UI 自动化测试框架,覆盖了用例设计、脚本开发、执行入口、报告生成等全流程,并且实现了代码复用、异常处理、截图追溯等关键功能。

        最后,自动化测试是一个 "实践出真知" 的领域,只有多动手、多踩坑,才能不断提升自己的技术水平。希望本文的实战案例能为你提供一些帮助,祝你在自动化测试的道路上越走越远!

Read more

QUEST一体机游戏下载和安装教程:SideQuest详细使用方法 QUEST一体机游戏安装教程、SideQuest使用方法、QUEST未知来源游戏安装、VR一体机安装APK、SideQuest安装O

QUEST一体机游戏下载和安装教程:SideQuest详细使用方法 QUEST一体机游戏安装教程、SideQuest使用方法、QUEST未知来源游戏安装、VR一体机安装APK、SideQuest安装O

QUEST一体机游戏下载和安装教程:SideQuest详细使用方法 SEO关键词:QUEST一体机游戏安装教程、SideQuest使用方法、QUEST未知来源游戏安装、VR一体机安装APK、SideQuest安装OBB数据包 在使用 QUEST 一体机过程中,很多用户会遇到一个问题:如何安装本地 APK 游戏?如何处理 OBB 数据包?安装后在哪里打开? 本文将完整梳理: * SideQuest 下载地址 * APK 安装流程 * OBB 数据包复制方法 * 游戏打开位置说明 内容尽量结构化说明,便于快速操作。 一、SideQuest中文版下载地址 下载地址: [https://pan.quark.cn/s/0b20dec578a3](https://pan.quark.cn/s/0b20dec578a3 建议转存后下载,避免因下载中断导致安装失败。 二、安装前准备 在正式安装前,请确认:

2026年RAG技术路线图:基于DeepSeek与Neo4j知识图谱构建企业智能体系

RAG的演进:为何图检索增强生成(GraphRAG)将主导2026年 检索增强生成(RAG)自问世以来经历了深刻变革,2026年标志着其向图检索增强生成(GraphRAG)范式的关键性转变。这一演进源于传统平面向量型RAG在满足企业级复杂推理和可靠决策支持需求方面日益凸显的局限性。 这一转型的核心驱动力是从平面向量相似性向复杂关系推理的跨越。传统RAG依赖向量嵌入来衡量查询与文档片段的语义相似性,但这种方法无法捕捉企业决策至关重要的实体、概念与事件间的复杂关联。相比之下,GraphRAG将信息构建为包含节点(实体)和边(关系)的知识图谱,使模型能够遍历并推理这些关联——解锁了平面向量RAG无法实现的多跳推理和上下文关系理解能力。 GraphRAG还解决了传统RAG的两大长期痛点:上下文窗口限制和“中间信息丢失”问题。随着企业查询日益复杂,需要更大的上下文窗口来整合相关信息,但即便是最先进的大语言模型(LLM)也存在有限的上下文容量。GraphRAG通过将结构化知识存储在外部图数据库中解决了这一问题,允许模型按需检索最相关的节点和关系,而非将大量文本塞入上下文窗口。此外,“中间信息

WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

WorkBuddy 使用指南:从零开始配置 QQ 机器人,解锁桌面智能体新玩法

文章目录 * 前言 * 下载 WorkBuddy * 认识 WorkBuddy * 插件类型 * 配置 QQ 机器人 * 登录 QQ 开放平台并注册激活账号 * 配置超级管理员、主体及认证信息 * 创建 QQ 机器人 * 获取 AppID 和 AppSecret * 从 Claw 中获取 Webhook * 在 QQ 开发平台配置回调地址 * 开始使用 WorkBuddy Claw * 总结 前言 在大家还在沉迷于如何搭建 OpenClaw 的时候,腾讯竟然悄悄公测了 WorkBuddy。这是一款面向全角色的桌面智能体,下达指令即可自动生成文档、表格、图表及 PPT 等可视化成果,能够自主规划并交付多模态复杂任务结果,支持多 Agents 并行工作,极致提效,

基于深度学习YOLO算法+qwen deepseek大模型的无人机河道巡检系统平台 支持AI对话与文档生成分析

基于深度学习YOLO算法+qwen deepseek大模型的无人机河道巡检系统平台 支持AI对话与文档生成分析

YOLO+DeepSeek河道环境检测系统 项目简介 基于改进YOLO深度学习模型与DeepSeek大语言模型的河道环境智能检测与分析系统。本系统采用先进的计算机视觉技术,结合自然语言处理能力,实现对河道环境中各类目标的高精度检测与智能分析。系统支持单张图片、批量图片、视频文件及实时摄像头等多种输入方式,提供从环境检测到智能建议的完整解决方案,为河道治理、环境保护与水资源管理提供智能化技术支持。 ✨ 核心亮点 • 多场景检测支持:全面覆盖单张图片、图片文件夹、视频文件、实时摄像头四种输入方式 • 改进YOLO模型:基于YOLOv5/v8/v11/v12的优化版本,专注河道环境目标检测 • AI智能分析:集成DeepSeek/Qwen大模型,生成专业的河道环境分析与治理建议 • 实时处理反馈:前端实时展示检测进度与结果,支持实时视频流处理 • 完整技术栈:PyTorch深度学习 + SpringBoot后端 + Vue3前端 + Flask中台的完整架构 • 开箱即用:提供完整源码、预训练模型与详细部署文档,快速上手使用 🌊 检测对象范围 系统可精准识别河道