# WebUI **Repository Path**: learnql/web-ui ## Basic Information - **Project Name**: WebUI - **Description**: 采用Unittest + Selenium + unittestreport + Typer搭建的UI自动化测试框架: 项目结构清晰,便于统一管理; 用例支持失败重试,多线程并发执行; 可生成3种不同风格的测试报告;并支持将报告及结果发送钉钉和企业微信; 加入Typer命令行应用,简单化用例执行,模型迁移等操作; - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-04-23 - **Last Updated**: 2025-04-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # UI自动化测试项目 ## 介绍 - 采用Unittest + Selenium + unittestreport + Typer搭建的自动化测试框架: - 项目结构清晰,便于统一管理; - 用例支持失败重试,多线程并发执行; - 可生成3种不同风格的测试报告;并支持将报告及结果发送钉钉和企业微信; - 加入Typer命令行应用,简单化用例执行,模型迁移等操作; - Unittest是Python标准库中自带的测试框架,它遵循了xUnit风格,即将测试组织成类和方法: - 兼容性好:Unittest不需要安装任何额外的包,可以在任何Python环境中使用。Unittest也可以与其他测试框架协作,如nose或doctest; - 结构化清晰:Unittest将测试分为三个阶段:setUp,test和tearDown。这样可以方便地设置和清理测试环境,以及管理测试依赖; - 丰富的断言方法:Unittest提供了很多专门的断言方法,可以覆盖各种情况,如检查相等性,包含性,异常抛出等; - Selenium 是支持 web 浏览器自动化的一系列工具和库的综合项目。它提供了扩展来模拟用户与浏览器的交互,用于扩展浏览器分配的分发服务器, 以及用于实现W3C WebDriver规范的基础结构, 该规范允许您为所有主要 Web 浏览器编写可互换的代码; - 测试可以使用以下任何一种语言编写:Java、Python、C#、PHP、Ruby 和 JavaScript; - 测试可以在以下任何操作系统中进行:Windows、Mac 或 Linux; - 可以使用任何浏览器进行测试:Mozilla、Firefox、IE、Chrome、Safari或Opera; - 开源且可移植; - 易于识别和使用网络元素; - unittestreport是柠檬班-木森老师基于unittest开发的的一个功能扩展库: - 支持3种风格HTML测试报告生成; - 支持测试用例失败重运行; - 多线程并发执行用例; - 支持发送测试结果及报告到邮箱; - 支持测试结果推送到钉钉及企业微信; unittest 官方文档: https://docs.python.org/zh-cn/3/library/unittest.html selenium 官方文档:https://www.selenium.dev/zh-cn/documentation/ unittestreport 官方文档:https://unittestreport.readthedocs.io/en/latest/ Typer 官方文档:https://typer.tiangolo.com/ ## 项目结构 - application:主项目配置目录,也存放了主路由文件 - env:基础环境配置文件 - prod.py:生产环境 - testing.py:测试环境 - routers.py:主路由文件,统一管理 - settings.py:主项目配置文件 - core:核心文件目录 - database.py:数据库操作配置 - driver.py:浏览器驱动 - page.py:页面基本操作 - unit_case.py:用例执行预置操作 - script: 脚本存放目录 - create_case:按模型生成测试用例 - create_pages:按模型生成测试页面对象服务 - utils:封装的一些工具类目录 - website:项目的测试内容存放目录 - datas:测试数据存放目录 - pages:测试页面对象服务 - menu:功能-菜单 - locators:页面定位元素 - operates:页面操作步骤 - test_cases:测试用例集 - menu:功能-菜单 目录 - test_case_name:测试用例执行文件 - logs:日志目录 - report:测试报告存放目录 - main.py:主程序入口文件 ## 开发环境 开发语言:Python 3.10 开发框架:unittest + selenium + unitestreport + Typer ## 开发工具 Pycharm 2023.2 推荐插件:Chinese (Simplified) Language Pack / 中文语言包 ## 使用 ``` # 安装依赖库 pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ # 第三方源: 1. 阿里源: https://mirrors.aliyun.com/pypi/simple/ 2. 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple/ 3. 中国科技大学: https://mirrors.bfsu.edu.cn/pypi/web/simple/ 4. 豆瓣: https://mirrors.cloud.tencent.com/pypi/simple/ ``` ### 初始化模型 ```shell # 项目根目录下执行,路径已存在时需要先删除 # 会自动将模型迁移到路径下,并按模型生成对应的.py文件 # 生成页面对象服务 # python main.py init-pages 功能/菜单 路径 python main.py init-pages user/list # 生成测试用例 # python main.py init-case 功能/菜单 路径 python main.py init-case user/list ``` ### 执行测试用例集 ```shell # 直接运行main文件run-test命令 python main.py run-test ``` ## 其他操作 用例规范 - 用例文件需存放于testCase目录下,且以test开头(示例:test_login.py); - 用例必须要写在类中,并且类要继承UnittestCase,类中用例名称要以test_开头; 用例默认执行顺序 - 同一层级的文件名通过ASC码排序,先找到的文件,先收集里面的用例; - 不同层级的文件:从外到内,即先把外层目录下的收集完成后,在找文件夹里面的; - 文件当中的用例执行顺序:从上到下,按照代码顺序; - 需要跳过用例不执行时,在用例方法中添加装饰器@unittest.skip即可; - 想要按照自定义顺序执行时: ``` 文件顺序:在test*.py中加入序号,如:test_01login.py,test_02register.py 用例顺序:在方法名称中加入序号,如:def test_01normal_login, def test_02empty_name_login ``` 代码示例: - 定义页面定位元素 ```python # locators.py from selenium.webdriver.common.by import By # 通过By方法获取定位信息 class PageLocators: """页面定位元素集""" username = (By.ID, 'Username') # 用户名,通过id定位 password = (By.NAME, 'Password') # 密码,通过name定位 login_btn = (By.XPATH, '/form/div[3]/div/button') # 登录按钮,通过xpath定位 success_mark = (By.CSS_SELECTOR, '#app > div > div.app-main > div') # 登录成功标识,通过CSS定位 fail_hint = (By.XPATH, '/html/body/div/div/div[1]/form/div[3]/div/div/ul/li') # 登录失败提示内容 ``` - 定义页面操作步骤 ```python # operates.py from core.page import BasePage from website.pages.login.locators import PageLocators from application.routers import Login from utils.get_time import sleep from selenium.common.exceptions import NoSuchElementException class PageOperate(BasePage): """页面操作""" loc = PageLocators() def open_login_page(self): """打开页面""" self.open_page(Login.login) def login(self, name, password): """ 登录事件 :param name: 用户名 :param password: 登录密码 """ # 显示等待,等待元素可见 self.wait_element_visible(self.loc.username) # 1.输入用户名 self.input_value(self.loc.username, name, '登录页面_用户名输入框') # 2.输入密码 self.input_value(self.loc.password, password, '登录页面_密码输入框') # 3.点击登录 self.click_element(self.loc.login_btn, '登录页面_登录按钮') def get_success_mark(self): """获取登录成功标识""" try: sleep(1) # 强制等待,避免找到元素但信息获取为空 return self.get_element_text(self.loc.success_mark, '登录成功标识') except NoSuchElementException: return def get_fail_mark(self): """获取登录失败标识""" # 检测页面仍停留在登录页面则表示登录失败 try: return self.get_element_text(self.loc.fail_hint, '登录失败提示信息') except NoSuchElementException: return ``` - 编写测试用例 ```python # test_login.py import os from application.settings import DATA_DIR from ddt import ddt, data, unpack, file_data # 引入数据驱动测试 from core.unit_case import * # 引入预置操作 from website.pages.login import operates # 引入页面操作步骤 from website.datas.login_data import LoginData # 引入登录页面驱动测试数据 @ddt # 数据驱动测试 class TestLogin(MyUnit): """登录测试""" @data(LoginData.correct_login_data) # 导入测试数据 @unpack # 对测试数据进行拆分 # @unittest.skip('skip this case') def test01_login_normal(self, values): """用例1: 输入正确的用户名密码可正常登录""" login_page = operates.PageOperate(self.driver) # 测试步骤 login_page.open() # 打开登录页面 login_page.login_opt(values['user'], values['pwd']) # 执行登录操作 # 断言 mark = login_page.get_success_mark() # 获取登录成功标识 self.assertEqual(values['expect'], mark) # 判断结果是否等于预期 @data(*LoginData.empty_login_data) # 导入测试数据 # @unittest.skip('skip this case') def test02_login_name_empty(self, values): """用例2: 用户名或密码为空不能正常登录""" login_page = operates.PageOperate(self.driver) # 测试步骤 login_page.open() # 打开登录页面 login_page.login_opt(values['user'], values['pwd']) # 执行登录操作 # 断言 mark = login_page.get_fail_mark() # 获取登录失败标识 self.assertEqual(values['expect'], mark) # 判断结果是否等于预期 @file_data(os.path.join(DATA_DIR, 'error_login_data.json')) # 导入外部文件中的测试数据 # @unittest.skip('skip this case') def test03_login_name_error(self, values): """用例3: 用户名或密码不正确不能正常登录""" login_page = operates.PageOperate(self.driver) # 测试步骤 value = values[0] # 获取参数值 login_page.open() # 打开登录页面 login_page.login_opt(value['user'], value['pwd']) # 执行登录操作 # 断言 mark = login_page.get_fail_mark() # 获取登录失败标识 self.assertEqual(value['expect'], mark) # 判断结果是否等于预期 if __name__ == '__main__': unittest.main() ```