初始提交

This commit is contained in:
焦龙言
2026-07-24 09:43:10 +08:00
commit b5b4721456
793 changed files with 116305 additions and 0 deletions
@@ -0,0 +1,165 @@
import time
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class ConfigManagementTest(BasePageTest):
"""参数设置测试类"""
def generate_config_data(self) -> dict:
"""生成测试数据"""
timestamp = int(time.time())
return {
'config_name': f'test_config_{timestamp}',
'config_key': f'test_config_key_{timestamp}',
'config_value': f'test_config_value_{timestamp}',
}
async def create_config(self, config_name: str, config_key: str, config_value: str) -> None:
"""创建参数配置"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').first.click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写参数信息
await dialog.get_by_role('textbox', name='参数名称').fill(config_name)
await dialog.get_by_role('textbox', name='参数键名').fill(config_key)
await dialog.get_by_role('textbox', name='参数键值').fill(config_value)
# 等待一段时间确保输入完成
await self.page.wait_for_timeout(500)
# 选择参数状态 - 选择系统内置选项为N(否),避免后端限制删除
# 在对话框中找到系统内置的单选按钮并选择
await self.wait_for_selector('input[type="radio"]', timeout=5000)
# 点击系统内置'否'选项
# 根据codegen脚本,使用更精确的定位器
await self.page.locator('label:nth-child(2) > .el-radio__input > .el-radio__inner').click()
# 等待一段时间确保选择完成
await self.page.wait_for_timeout(500)
# 点击确认按钮
confirm_button = self.page.get_by_role('button', name='确 定').first
await confirm_button.click()
# 等待一段时间确保请求发送
await self.page.wait_for_timeout(1000)
# 等待新增成功提示
await self.wait_for_selector('div:has-text("新增成功")', timeout=10000)
async def search_config(self, config_name: str, config_key: str = '') -> None:
"""搜索参数配置"""
# 在查询表单中输入参数信息进行查询
await self.page.get_by_role('textbox', name='参数名称').fill(config_name)
if config_key:
await self.page.get_by_role('textbox', name='参数键名').fill(config_key)
# 点击搜索按钮
await self.page.get_by_role('button', name='搜索').first.click()
# 等待搜索结果加载
await self.page.wait_for_timeout(1000)
async def edit_config(self, updated_value: str) -> None:
"""编辑参数配置"""
# 点击编辑按钮
await self.page.locator('tbody').get_by_role('button', name='修改').nth(0).click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改参数键值
await dialog.get_by_role('textbox', name='参数键值').fill(updated_value)
# 确认
await self.page.get_by_role('button', name='确 定').first.click()
# 等待编辑成功提示
await self.wait_for_selector('div:has-text("修改成功")', timeout=10000)
async def delete_config(self) -> None:
"""删除参数配置"""
# 点击删除按钮
await self.page.locator('tbody').get_by_role('button', name='删除').nth(0).click()
# 确认删除
await self.page.get_by_role('button', name='确定').first.click()
# 等待删除成功提示
await self.wait_for_selector('div:has-text("删除成功")', timeout=10000)
# 重置查询表单,确保能看到所有参数
reset_button = self.page.get_by_role('button', name='重置')
await reset_button.click()
# 等待列表刷新
await self.page.wait_for_timeout(1000)
async def test_config_crud_operations(self) -> None:
"""测试参数设置的增删查改功能"""
# 访问参数设置页面
await self.goto_page(Config.frontend_url + '/system/config')
# 等待页面加载完成
await self.wait_for_page_title('参数设置', timeout=10000)
await self.page.wait_for_timeout(1000) # 等待列表刷新
# 记录初始参数数量
initial_config_count = await self.get_table_total_rows()
# 生成测试数据
data = self.generate_config_data()
# 1.新增
await self.create_config(data['config_name'], data['config_key'], data['config_value'])
# 验证新增
await self.page.wait_for_timeout(1000) # 等待列表刷新
new_config_count = await self.get_table_total_rows()
assert new_config_count > initial_config_count, '新增参数后数量应该增加'
# 2.搜索
await self.search_config(data['config_name'], data['config_key'])
# 验证搜索
search_result_count = await self.get_table_total_rows()
assert search_result_count >= 1, '搜索结果应该至少包含一个参数'
# 3.编辑
await self.edit_config(f'updated_config_value_{time.time()}')
# 验证编辑
await self.page.wait_for_timeout(1000) # 等待列表刷新
# 重新搜索以验证修改结果
await self.search_config(data['config_name'])
# 4.删除
await self.delete_config()
# 验证删除
await self.page.wait_for_timeout(1000) # 等待列表刷新
final_config_count = await self.get_table_total_rows()
assert final_config_count == initial_config_count, '删除参数后数量应该恢复到初始值'
@pytest.mark.asyncio
async def test_config_management_page() -> None:
"""测试参数设置页面功能"""
async with async_playwright() as p:
test_instance = ConfigManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_config_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,135 @@
import time
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class DeptManagementTest(BasePageTest):
"""部门管理页面测试类"""
def generate_dept_data(self) -> dict:
"""生成测试数据"""
timestamp = int(time.time())
return {
'dept_name': f'测试部门_{timestamp}',
'order_num': '3',
'leader_name': '年糕',
}
async def create_dept(self, dept_name: str, order_num: str) -> None:
"""创建部门"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').first.click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 上级部门
# 使用 label 定位父级 form-item,再点击内部的 wrapper
await dialog.locator('div.el-form-item').filter(has_text='上级部门').locator('.el-select__wrapper').click()
# 选择 "集团总公司" (根节点)
# 使用 .el-popper 定位下拉框中的内容
await self.page.locator('.el-popper:visible').get_by_text('集团总公司').click()
# 填写部门名称
await dialog.get_by_role('textbox', name='部门名称').fill(dept_name)
# 填写显示排序
# el-input-number 通常是一个 spinbutton 或者 input
await dialog.get_by_role('spinbutton', name='显示排序').fill(order_num)
# 点击确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功消息
await self.wait_for_selector("div:has-text('成功')", timeout=10000)
async def search_dept(self, dept_name: str) -> None:
"""搜索部门"""
search_form = self.page.locator('form').first
await search_form.get_by_role('textbox', name='部门名称').fill(dept_name)
# 点击搜索按钮
await self.page.get_by_role('button', name='搜索').first.click()
# 等待加载
await self.page.wait_for_timeout(1000)
async def edit_dept(self, leader_name: str) -> None:
"""编辑部门"""
# 点击修改按钮 (第一行)
# 树形表格结构可能不同,但通常也是在 tbody 的 row 中
await self.page.locator('tbody').get_by_role('button', name='修改').nth(0).click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改负责人
await dialog.get_by_role('textbox', name='负责人').fill(leader_name)
# 确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("修改成功")', timeout=10000)
async def delete_dept(self) -> None:
"""删除部门"""
# 点击删除按钮
await self.page.locator('tbody').get_by_role('button', name='删除').nth(0).click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("删除成功")', timeout=10000)
# 重置搜索
await self.page.get_by_role('button', name='重置').click()
await self.page.wait_for_timeout(1000)
async def test_dept_crud_operations(self) -> None:
"""测试部门管理增删查改"""
# 访问页面
await self.goto_page(Config.frontend_url + '/system/dept')
await self.wait_for_page_title('部门管理', timeout=10000)
# 生成测试数据
data = self.generate_dept_data()
# 1. 新增
await self.create_dept(data['dept_name'], data['order_num'])
# 验证新增
await self.search_dept(data['dept_name'])
# 树形表格,只要有行显示即可
rows = await self.page.query_selector_all('.el-table__row')
assert len(rows) >= 1, '新增后搜索应有结果'
# 2. 编辑
await self.edit_dept(data['leader_name'])
# 重新搜索以验证修改结果
await self.search_dept(data['dept_name'])
# 3. 删除
await self.delete_dept()
# 验证删除
await self.search_dept(data['dept_name'])
rows_after = await self.page.query_selector_all('.el-table__row')
assert len(rows_after) == 0, '删除后搜索应无结果'
@pytest.mark.asyncio
async def test_dept_management_page() -> None:
"""测试部门管理页面功能"""
async with async_playwright() as p:
test_instance = DeptManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_dept_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,201 @@
from datetime import datetime
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class DictManagementTest(BasePageTest):
"""字典管理测试类"""
def generate_dict_data(self) -> dict:
"""生成测试数据"""
timestamp = datetime.now().strftime('%H%M%S')
return {
'dict_name': f'测试字典_{timestamp}',
'dict_type': f'test_dict_{timestamp}',
'dict_data_label_1': '正常',
'dict_data_value_1': '1',
'dict_data_label_2': '异常',
'dict_data_value_2': '2',
'remark': f'备注_{timestamp}',
}
async def create_dict_type(self, dict_name: str, dict_type: str) -> None:
"""创建字典类型"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').first.click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写表单
await dialog.get_by_role('textbox', name='字典名称').fill(dict_name)
await dialog.get_by_role('textbox', name='字典类型').fill(dict_type)
# 提交
await dialog.get_by_role('button', name='确 定').click()
# 验证成功
await self.wait_for_selector("div:has-text('新增成功')", timeout=10000)
async def search_dict_type(self, dict_name: str, dict_type: str) -> None:
"""搜索字典类型"""
form = self.page.locator('form').first
await form.get_by_role('textbox', name='字典名称').fill(dict_name)
await form.get_by_role('textbox', name='字典类型').fill(dict_type)
# 点击搜索按钮
await self.page.get_by_role('button', name='搜索').click()
await self.page.wait_for_timeout(1000)
async def edit_dict_type(self, dict_name: str, remark: str) -> None:
"""修改字典类型"""
await self.search_dict_type(dict_name, '')
# 点击修改按钮
row = self.page.locator('tbody tr').first
await row.get_by_role('button', name='修改').click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
await dialog.get_by_role('textbox', name='备注').fill(remark)
await dialog.get_by_role('button', name='确 定').click()
await self.wait_for_selector("div:has-text('修改成功')", timeout=10000)
async def manage_dict_data(self, dict_type: str, data: dict) -> None:
"""管理字典数据"""
# 1. 进入字典数据页面
# 点击字典类型链接
row = self.page.locator('tbody tr').first
await row.get_by_role('link', name=dict_type).click()
# 等待页面切换(Playwright在SPA中通常不需要处理多标签页,除非新开tab)
# 这里是 router-link,应该是页内跳转
# 检查是否到了字典数据页面
# 或者等待URL变化,或者等待特定元素
# data.vue 有 "关闭" 按钮
await self.wait_for_selector("button:has-text('关闭')", timeout=5000)
# 2. 新增数据1 (正常)
await self.create_dict_data(data['dict_data_label_1'], data['dict_data_value_1'], '1', '主要(primary)')
# 3. 新增数据2 (异常)
await self.create_dict_data(data['dict_data_label_2'], data['dict_data_value_2'], '2', '危险(danger)')
# 4. 修改数据
await self.edit_dict_data(data['dict_data_label_2'], '测试备注')
# 5. 删除数据
await self.delete_dict_data(data['dict_data_label_2'])
await self.delete_dict_data(data['dict_data_label_1'])
# 6. 关闭页面 (返回字典类型)
await self.page.get_by_role('button', name='关闭', exact=True).click()
# 等待返回
await self.wait_for_selector('text=字典名称') # 假设返回后能看到原来的页面元素
async def create_dict_data(self, label: str, value: str, sort: str, list_class: str) -> None:
"""创建字典数据"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').first.click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
await dialog.get_by_role('textbox', name='数据标签').fill(label)
await dialog.get_by_role('textbox', name='数据键值').fill(value)
await dialog.get_by_role('spinbutton', name='显示排序').fill(sort)
# 选择回显样式
# 点击下拉框触发器
# el-select 的结构通常是 wrapper -> input (readonly)
# 我们可以点击 label 为 "回显样式" 的 form-item 下的 select
select_trigger = dialog.locator("label:has-text('回显样式') + div .el-select")
if await select_trigger.count() == 0:
# 尝试直接找 .el-select,可能布局不同
select_trigger = dialog.locator('.el-select').filter(has_text='默认(default)').first
# 如果还是找不到,尝试点击那个默认值文本
if await select_trigger.count() == 0:
await dialog.get_by_text('默认(default)').click()
else:
await select_trigger.click()
# 选择选项 (选项在 body 的 el-popper 中)
# 等待选项出现
await self.page.get_by_role('option', name=list_class).click()
await dialog.get_by_role('button', name='确 定').click()
await self.wait_for_selector("div:has-text('新增成功')", timeout=10000)
async def edit_dict_data(self, label: str, remark: str) -> None:
"""修改字典数据"""
# 点击修改按钮
row = self.page.locator('tbody tr').filter(has_text=label).first
await row.get_by_role('button', name='修改').click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
await dialog.get_by_role('textbox', name='备注').fill(remark)
await dialog.get_by_role('button', name='确 定').click()
await self.wait_for_selector("div:has-text('修改成功')", timeout=10000)
async def delete_dict_data(self, label: str) -> None:
"""删除字典数据"""
# 点击删除按钮
row = self.page.locator('tbody tr').filter(has_text=label).first
await row.get_by_role('button', name='删除').click()
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector("div:has-text('删除成功')", timeout=10000)
async def delete_dict_type(self, dict_name: str) -> None:
"""删除字典类型"""
await self.search_dict_type(dict_name, '')
# 点击删除按钮
row = self.page.locator('tbody tr').filter(has_text=dict_name).first
await row.get_by_role('button', name='删除').click()
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector("div:has-text('删除成功')", timeout=10000)
async def test_dict_crud_operations(self) -> None:
"""测试字典管理全流程"""
data = self.generate_dict_data()
# 1. 进入字典管理
await self.goto_page(Config.frontend_url + '/system/dict')
# 等待页面加载
await self.wait_for_selector('text=字典名称') # 页面标题或其他标识
# 2. 创建字典类型
await self.create_dict_type(data['dict_name'], data['dict_type'])
# 3. 搜索并修改
await self.edit_dict_type(data['dict_name'], data['remark'])
# 4. 管理字典数据
await self.manage_dict_data(data['dict_type'], data)
# 5. 删除字典类型
await self.delete_dict_type(data['dict_name'])
@pytest.mark.asyncio
async def test_dict_management_page() -> None:
"""测试字典管理页面功能"""
async with async_playwright() as p:
test_instance = DictManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_dict_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,138 @@
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class LogManagementTest(BasePageTest):
"""日志管理测试类"""
async def test_operlog_operations(self) -> None:
"""测试操作日志查询、删除和清空"""
# 1. 导航到操作日志页面
# 直接访问正确的路由
await self.page.goto(Config.frontend_url + '/system/log/operlog')
await self.page.wait_for_load_state('networkidle')
try:
await self.page.wait_for_selector('.app-container', timeout=10000)
except Exception:
# 如果首次加载失败,尝试刷新
await self.page.reload()
await self.page.wait_for_selector('.app-container', timeout=10000)
# 2. 查询操作
# 输入操作人员
await self.page.locator('div.el-form-item', has_text='操作人员').locator('input').fill('admin')
# 点击搜索按钮
await self.page.get_by_role('button', name='搜索').click()
await self.page.wait_for_timeout(1000) # 等待搜索结果
# 点击重置按钮
await self.page.get_by_role('button', name='重置').click()
await self.page.wait_for_timeout(1000) # 等待重置结果
# 3. 删除操作
# 检查是否有数据,如果有则进行删除测试
rows = self.page.locator('.el-table__body tr')
count = await rows.count()
if count > 0:
# 选中第一条
await rows.first.locator('.el-checkbox').click()
# 点击删除
await self.page.get_by_role('button', name='删除').click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待删除完成提示
try:
await self.page.wait_for_selector('text=删除成功', timeout=10000)
except Exception:
pass # 可能是 toast 提示,稍纵即逝
# 4. 清空操作
# 点击清空
await self.page.get_by_role('button', name='清空').click()
# 确认清空
await self.page.get_by_role('button', name='确定').click()
# 5. 验证清空后还有一条"清空"的操作日志
await self.page.wait_for_timeout(1000) # 等待清空操作完成并刷新
await self.page.get_by_role('button', name='搜索').click() # 刷新列表
await self.page.wait_for_timeout(1000)
# 验证列表不为空 (因为清空操作本身会产生一条日志)
rows_after_clean = self.page.locator('.el-table__body tr')
count_after_clean = await rows_after_clean.count()
assert count_after_clean > 0, '操作日志清空后应该至少有一条记录(清空操作本身)'
# 可以在这里进一步验证第一条记录的类型是否为清空,但题目只要求不是空列表
async def test_logininfor_operations(self) -> None:
"""测试登录日志查询、删除和清空"""
# 1. 导航到登录日志页面
await self.page.goto(Config.frontend_url + '/system/log/logininfor')
await self.page.wait_for_load_state('networkidle')
try:
await self.page.wait_for_selector('.app-container', timeout=10000)
except Exception:
# 如果失败,尝试刷新
await self.page.reload()
await self.page.wait_for_selector('.app-container', timeout=10000)
# 2. 查询操作
# 输入用户名称
await self.page.locator('div.el-form-item', has_text='用户名称').locator('input').fill('admin')
# 点击搜索
await self.page.get_by_role('button', name='搜索').click()
await self.page.wait_for_timeout(1000)
# 点击重置
await self.page.get_by_role('button', name='重置').click()
await self.page.wait_for_timeout(1000)
# 3. 删除操作
# 检查是否有数据
rows = self.page.locator('.el-table__body tr')
count = await rows.count()
if count > 0:
# 选中第一条
await rows.first.locator('.el-checkbox').click()
# 点击删除
await self.page.get_by_role('button', name='删除').click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待删除成功
try:
await self.page.wait_for_selector('text=删除成功', timeout=10000)
except Exception:
pass
# 4. 清空操作
# 点击清空
await self.page.get_by_role('button', name='清空').click()
# 确认清空
await self.page.get_by_role('button', name='确定').click()
# 5. 验证清空后列表为空
await self.page.wait_for_timeout(1000)
# 检查是否显示"暂无数据"
no_data = await self.page.get_by_text('暂无数据').is_visible()
if not no_data:
# 再次检查行数
count_final = await self.page.locator('.el-table__body tr').count()
assert count_final == 0, '登录日志清空后应该没有数据'
@pytest.mark.asyncio
async def test_log_management_page() -> None:
"""测试日志管理页面功能"""
async with async_playwright() as p:
test_instance = LogManagementTest()
await test_instance.setup(p)
try:
# 运行操作日志测试
await test_instance.test_operlog_operations()
# 运行登录日志测试
await test_instance.test_logininfor_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,145 @@
from datetime import datetime
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class MenuManagementTest(BasePageTest):
"""菜单管理测试"""
def generate_menu_data(self) -> dict:
"""生成测试数据"""
timestamp = datetime.now().strftime('%H%M%S')
return {
'dir_name': f'测试目录_{timestamp}',
'menu_name': f'测试菜单_{timestamp}',
'path_dir': f'test_dir_{timestamp}',
'path_menu': f'test_menu_{timestamp}',
'path_menu_new': f'test_menu_new_{timestamp}',
'order_num': '10',
}
async def create_directory(self, menu_name: str, path: str, order_num: str) -> None:
"""创建目录"""
# 点击顶部新增按钮
await self.page.get_by_role('button', name='新增').first.click()
# 等待对话框出现
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写表单
await dialog.get_by_role('spinbutton', name='显示排序').fill(order_num)
await dialog.get_by_role('textbox', name='菜单名称').fill(menu_name)
await dialog.get_by_role('textbox', name='路由地址').fill(path)
# 提交
await dialog.get_by_role('button', name='确 定').click()
# 验证成功提示
await self.wait_for_selector("div:has-text('新增成功')", timeout=10000)
async def search_menu(self, menu_name: str) -> None:
"""搜索菜单"""
# 填写搜索条件
# 使用 first 匹配搜索表单(通常在顶部),避免匹配到隐藏对话框中的同名字段
await self.page.locator('form').first.get_by_role('textbox', name='菜单名称').fill(menu_name)
await self.page.get_by_role('button', name='搜索').click()
# 等待表格刷新(简单等待)
await self.page.wait_for_timeout(1000)
async def create_sub_menu(self, parent_name: str, menu_name: str, path: str, order_num: str) -> None:
"""在指定目录下创建子菜单"""
# 找到父级菜单行
row = self.page.locator('tbody tr').filter(has_text=parent_name).first
# 点击行内新增按钮
await row.get_by_role('button', name='新增').click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 选择菜单类型为"菜单"
await dialog.locator('label').filter(has_text='菜单').first.click()
# 填写表单
await dialog.get_by_role('spinbutton', name='显示排序').fill(order_num)
await dialog.get_by_role('textbox', name='菜单名称').fill(menu_name)
await dialog.get_by_role('textbox', name='路由地址').fill(path)
# 提交
await dialog.get_by_role('button', name='确 定').click()
# 验证成功
await self.wait_for_selector("div:has-text('新增成功')", timeout=10000)
async def edit_sub_menu(self, menu_name: str, new_path: str) -> None:
"""修改子菜单"""
# 搜索子菜单
await self.search_menu(menu_name)
row = self.page.locator('tbody tr').filter(has_text=menu_name).first
await row.get_by_role('button', name='修改').click()
await self.wait_for_selector('.el-dialog', timeout=5000)
dialog = self.page.locator('.el-dialog')
await dialog.get_by_role('textbox', name='路由地址').fill(new_path)
await self.page.get_by_role('button', name='确 定').click()
await self.wait_for_selector("div:has-text('修改成功')", timeout=10000)
async def delete_menus(self, menu_names: list[str]) -> None:
"""删除菜单(列表)"""
for name in menu_names:
await self.search_menu(name)
row = self.page.locator('tbody tr').filter(has_text=name).first
# 点击删除
await row.get_by_role('button', name='删除').click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector("div:has-text('删除成功')", timeout=10000)
async def test_menu_crud_operations(self) -> None:
"""测试菜单增删改查流程"""
data = self.generate_menu_data()
# 1. 进入菜单管理页面
await self.goto_page(Config.frontend_url + '/system/menu')
await self.wait_for_page_title('菜单管理', timeout=10000)
# 2. 新增目录
await self.create_directory(data['dir_name'], data['path_dir'], data['order_num'])
# 3. 搜索目录并新增子菜单
await self.search_menu(data['dir_name'])
await self.create_sub_menu(data['dir_name'], data['menu_name'], data['path_menu'], '1')
# 4. 修改子菜单
await self.edit_sub_menu(data['menu_name'], data['path_menu_new'])
# 5. 删除子菜单
await self.delete_menus([data['menu_name']])
# 6. 删除目录
await self.delete_menus([data['dir_name']])
@pytest.mark.asyncio
async def test_menu_management_page() -> None:
"""测试菜单管理页面功能"""
async with async_playwright() as p:
test_instance = MenuManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_menu_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,165 @@
import re
import time
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class NoticeManagementTest(BasePageTest):
"""通知公告管理测试类"""
def generate_notice_data(self) -> dict:
"""生成测试数据"""
timestamp = int(time.time())
return {
'notice_title': f'test_notice_{timestamp}',
'updated_title': f'updated_notice_{timestamp}',
}
async def create_notice(self, notice_title: str, notice_type: int = 1, notice_content: str = '测试内容') -> None:
"""创建通知公告"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').first.click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写公告信息
await dialog.get_by_role('textbox', name='公告标题').fill(notice_title)
# 选择公告类型
# 根据codegen脚本,点击下拉框触发器
await self.page.locator('div').filter(has_text=re.compile(r'^请选择$')).nth(4).click()
# 选择对应类型选项,1为通知,2为公告
if notice_type == 1:
await self.page.get_by_role('option', name='通知').click()
else:
await self.page.get_by_role('option', name='公告').click()
# 填写公告内容
await self.page.locator('.ql-container').click()
await self.page.locator('.ql-editor').fill(notice_content)
# 等待一段时间确保输入完成
await self.page.wait_for_timeout(500)
# 点击确认按钮
await self.page.get_by_role('button', name='确 定').first.click()
# 等待一段时间确保请求发送
await self.page.wait_for_timeout(1000)
# 等待新增成功提示
await self.wait_for_selector('div:has-text("新增成功")', timeout=10000)
async def search_notice(self, notice_title: str) -> None:
"""搜索通知公告"""
# 在查询表单中输入公告标题进行查询
await self.page.get_by_role('textbox', name='公告标题').fill(notice_title)
# 点击搜索按钮
await self.page.get_by_role('button', name='搜索').first.click()
# 等待搜索结果加载
await self.page.wait_for_timeout(1000)
async def edit_notice(self, updated_title: str, updated_content: str = '更新的测试内容') -> None:
"""编辑通知公告"""
# 点击编辑按钮
await self.page.locator('tbody').get_by_role('button', name='修改').nth(0).click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改公告标题
await dialog.get_by_role('textbox', name='公告标题').fill(updated_title)
# 修改公告内容
await self.page.locator('.ql-editor').fill(updated_content)
# 点击确认按钮
await self.page.get_by_role('button', name='确 定').first.click()
# 等待编辑成功提示
await self.wait_for_selector('div:has-text("修改成功")', timeout=10000)
async def delete_notice(self) -> None:
"""删除通知公告"""
# 点击删除按钮
await self.page.locator('tbody').get_by_role('button', name='删除').nth(0).click()
# 确认删除
await self.page.get_by_role('button', name='确定').first.click()
# 等待删除成功提示
await self.wait_for_selector('div:has-text("删除成功")', timeout=10000)
# 重置查询表单,确保能看到所有公告
await self.page.get_by_role('button', name='重置').click()
# 等待列表刷新
await self.page.wait_for_timeout(1000)
async def test_notice_crud_operations(self) -> None:
"""测试通知公告的增删查改功能"""
# 访问通知公告页面
await self.goto_page(Config.frontend_url + '/system/notice')
# 等待页面加载完成
await self.wait_for_page_title('通知公告', timeout=10000)
await self.page.wait_for_timeout(1000) # 等待列表刷新
# 记录初始公告数量
initial_notice_count = await self.get_table_total_rows()
# 生成测试数据
data = self.generate_notice_data()
# 创建公告
await self.create_notice(data['notice_title'])
# 验证公告已添加到列表中
await self.page.wait_for_timeout(1000) # 等待列表刷新
new_notice_count = await self.get_table_total_rows()
assert new_notice_count > initial_notice_count, '新增公告后数量应该增加'
# 搜索公告
await self.search_notice(data['notice_title'])
# 验证搜索结果中包含新增的公告
search_result_count = await self.get_table_total_rows()
assert search_result_count >= 1, '搜索结果应该至少包含一个公告'
# 编辑公告
await self.edit_notice(data['updated_title'])
# 验证编辑成功
await self.page.wait_for_timeout(1000) # 等待列表刷新
# 重新搜索以验证修改结果
await self.search_notice(data['updated_title'])
# 删除公告
await self.delete_notice()
# 验证公告已从列表中删除
await self.page.wait_for_timeout(1000) # 等待列表刷新
final_notice_count = await self.get_table_total_rows()
assert final_notice_count == initial_notice_count, '删除公告后数量应该恢复到初始值'
@pytest.mark.asyncio
async def test_notice_management_page() -> None:
"""测试通知公告页面功能"""
async with async_playwright() as p:
test_instance = NoticeManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_notice_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,136 @@
from datetime import datetime
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class PostManagementTest(BasePageTest):
"""岗位管理测试类"""
def generate_post_data(self) -> dict:
"""生成测试数据"""
timestamp = datetime.now().strftime('%H%M%S')
return {
'post_name': f'测试岗位_{timestamp}',
'post_code': f'test_{timestamp}',
'post_sort': '10',
'remark': f'测试备注_{timestamp}',
}
async def test_post_crud_operations(self) -> None:
"""测试岗位增删改查流程"""
# 1. 导航到岗位管理页面
await self.navigate_to_post_management()
# 生成测试数据
data = self.generate_post_data()
# 2. 新增岗位
await self.create_post(data['post_name'], data['post_code'], data['post_sort'])
# 3. 搜索岗位
await self.search_post(data['post_name'], data['post_code'])
# 4. 修改岗位
await self.edit_post(data['post_name'], data['remark'])
# 5. 删除岗位
await self.delete_post(data['post_name'])
async def navigate_to_post_management(self) -> None:
"""导航到岗位管理"""
# 直接导航到岗位管理页面
await self.goto_page(Config.frontend_url + '/system/post')
# 等待页面加载
await self.wait_for_selector('.app-container')
async def create_post(self, post_name: str, post_code: str, post_sort: str) -> None:
"""新增岗位"""
await self.page.get_by_role('button', name='新增').click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写表单
# 注意:Element Plus 表单必填项可能有 * 号,Label 匹配可能需要注意
await dialog.get_by_role('textbox', name='岗位名称').fill(post_name)
await dialog.get_by_role('textbox', name='岗位编码').fill(post_code)
# 岗位顺序是 input-number
# script.py 使用 spinbutton
await dialog.get_by_role('spinbutton', name='岗位顺序').fill(post_sort)
await dialog.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector("div:has-text('新增成功')", timeout=10000)
async def search_post(self, post_name: str, post_code: str) -> None:
"""搜索岗位"""
# 填写搜索表单
# 限定在搜索区域,防止定位到弹窗或其他地方(虽然此时没有弹窗)
search_form = self.page.locator('.el-form').first
await search_form.get_by_role('textbox', name='岗位编码').fill(post_code)
await search_form.get_by_role('textbox', name='岗位名称').fill(post_name)
await self.page.get_by_role('button', name='搜索').click()
# 等待表格加载结果
# 验证表格中包含刚创建的岗位
await self.wait_for_selector(f"tbody tr:has-text('{post_name}')", timeout=10000)
async def edit_post(self, post_name: str, remark: str) -> None:
"""修改岗位"""
# 确保当前显示的是我们要修改的岗位
await self.search_post(post_name, '')
row = self.page.locator('tbody tr').first
# 点击修改按钮
await row.get_by_role('button', name='修改').click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改备注
await dialog.get_by_role('textbox', name='备注').fill(remark)
await dialog.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector("div:has-text('修改成功')", timeout=10000)
async def delete_post(self, post_name: str) -> None:
"""删除岗位"""
# 确保当前显示的是我们要删除的岗位
search_form = self.page.locator('.el-form').first
await search_form.get_by_role('textbox', name='岗位编码').clear()
await search_form.get_by_role('textbox', name='岗位名称').fill(post_name)
await self.page.get_by_role('button', name='搜索').click()
await self.wait_for_selector(f"tbody tr:has-text('{post_name}')", timeout=10000)
row = self.page.locator('tbody tr').first
# 点击删除按钮
await row.get_by_role('button', name='删除').click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待成功提示
await self.wait_for_selector("div:has-text('删除成功')", timeout=10000)
@pytest.mark.asyncio
async def test_post_management_page() -> None:
"""测试岗位管理页面功能"""
async with async_playwright() as p:
test_instance = PostManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_post_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,148 @@
import time
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class RoleManagementTest(BasePageTest):
"""角色管理测试类"""
def generate_role_data(self) -> dict:
"""生成测试数据"""
timestamp = int(time.time())
return {
'role_name': f'test_role_{timestamp}',
'role_key': f'test_{timestamp}',
}
async def create_role(self, role_name: str, role_key: str, role_sort: int = 3) -> None:
"""创建角色"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写信息
await dialog.get_by_role('textbox', name='角色名称').fill(role_name)
await dialog.get_by_role('textbox', name='权限字符').fill(role_key)
await dialog.get_by_role('spinbutton', name='角色顺序').fill(str(role_sort))
# 选择菜单权限 (点击第一个复选框)
await self.page.locator('.el-tree-node__content .el-checkbox').first.click()
# 确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("新增成功")', timeout=10000)
async def search_role(self, role_name: str, role_key: str) -> None:
"""搜索角色"""
search_form = self.page.locator('form').first
await search_form.get_by_placeholder('请输入角色名称').fill(role_name)
await search_form.get_by_placeholder('请输入权限字符').fill(role_key)
await search_form.get_by_role('button', name='搜索').click()
# 等待加载
await self.page.wait_for_timeout(1000)
async def edit_role(self, new_remark: str) -> None:
"""编辑角色"""
# 点击修改按钮 (第一行)
# 按钮没有文字,只有图标,所以使用 nth(0)
row = self.page.locator('tbody tr').first
await row.get_by_role('button').nth(0).click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改备注
await dialog.get_by_role('textbox', name='备注').fill(new_remark)
# 确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("修改成功")', timeout=10000)
async def change_role_status(self) -> None:
"""修改角色状态"""
# 点击开关 (第一行)
# 使用 .el-switch__core 或者 role=switch
# script.py uses .el-switch__action, let's try .el-switch
switch = self.page.locator('.el-switch').first
await switch.click()
# 确认对话框
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector('div:has-text("成功")', timeout=5000)
# 再次点击恢复
await self.page.wait_for_timeout(1000)
await switch.click()
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector('div:has-text("成功")', timeout=5000)
async def delete_role(self) -> None:
"""删除角色"""
# 点击删除按钮 (第一行第二个按钮)
row = self.page.locator('tbody tr').first
await row.get_by_role('button').nth(1).click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("删除成功")', timeout=10000)
# 重置搜索
await self.page.get_by_role('button', name='重置').click()
await self.page.wait_for_timeout(1000)
async def test_role_crud_operations(self) -> None:
"""测试角色管理CRUD"""
# 访问页面
await self.goto_page(Config.frontend_url + '/system/role')
await self.wait_for_page_title('角色管理', timeout=10000)
# 生成测试数据
data = self.generate_role_data()
# 1. 新增
await self.create_role(data['role_name'], data['role_key'])
# 验证新增结果
await self.search_role(data['role_name'], data['role_key'])
rows = await self.get_table_total_rows()
assert rows >= 1, '新增后搜索应有结果'
# 2. 修改状态
await self.change_role_status()
# 3. 编辑
await self.edit_role('Updated remark')
# 4. 删除
await self.delete_role()
# 验证删除结果
await self.search_role(data['role_name'], data['role_key'])
rows_after = await self.get_table_total_rows()
assert rows_after == 0, '删除后搜索应无结果'
@pytest.mark.asyncio
async def test_role_management_page() -> None:
"""测试角色管理页面功能"""
async with async_playwright() as p:
test_instance = RoleManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_role_crud_operations()
finally:
await test_instance.teardown()
@@ -0,0 +1,170 @@
import time
import pytest
from playwright.async_api import async_playwright
from common.base_page_test import BasePageTest
from common.config import Config
class UserManagementTest(BasePageTest):
"""用户管理测试类"""
def generate_user_data(self) -> dict:
"""生成测试数据"""
timestamp = int(time.time())
return {
'user_name': f'test_{timestamp}',
'nick_name': f'测试用户_{timestamp}',
'phone': '13888888888',
}
async def create_user(self, user_name: str, nick_name: str, phone: str) -> None:
"""创建用户"""
# 点击新增按钮
await self.page.get_by_role('button', name='新增').click()
# 等待对话框
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 填写基本信息
await dialog.get_by_role('textbox', name='用户昵称').fill(nick_name)
# 归属部门
# 使用 label 定位父级 form-item,再点击内部的 wrapper
await dialog.locator('div.el-form-item').filter(has_text='归属部门').locator('.el-select__wrapper').click()
# 选择 "集团总公司" (根节点)
# 使用 .el-popper 定位下拉框中的内容
await self.page.locator('.el-popper:visible').get_by_text('集团总公司').click()
await dialog.get_by_role('textbox', name='用户名称').fill(user_name)
# 手机号码在搜索框也有,所以必须限定在 dialog 内
# 注意:有些 label 可能不带 *,或者带 * 但 get_by_role 需要准确匹配
# 这里使用 get_by_placeholder 或者 filter 来定位更稳妥,或者直接 scope 到 dialog
await dialog.locator('div.el-form-item').filter(has_text='手机号码').get_by_role('textbox').fill(phone)
await dialog.locator('div.el-form-item').filter(has_text='邮箱').get_by_role('textbox').fill('test@example.com')
await dialog.locator('div.el-form-item').filter(has_text='用户密码').get_by_role('textbox').fill('123456')
# 用户性别
# 定位 label 为 "用户性别" 的父级 form-item,然后找里面的 "请选择" 文本
await dialog.locator('div.el-form-item').filter(has_text='用户性别').get_by_text('请选择').click()
await self.page.get_by_role('option', name='男').click()
# 岗位
await dialog.locator('div.el-form-item').filter(has_text='岗位').get_by_text('请选择').click()
await self.page.get_by_role('option', name='董事长').click()
# 角色
await dialog.locator('div.el-form-item').filter(has_text='角色').get_by_text('请选择').click()
await self.page.get_by_role('option', name='普通角色').click()
await dialog.locator('div.el-form-item').filter(has_text='备注').get_by_role('textbox').fill('测试用户')
# 点击确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功消息
await self.wait_for_selector("div:has-text('成功')", timeout=10000)
async def search_user(self, user_name: str) -> None:
"""搜索用户"""
search_form = self.page.locator('form').first
await search_form.get_by_role('textbox', name='用户名称').fill(user_name)
await search_form.get_by_role('button', name='搜索').click()
# 等待加载
await self.page.wait_for_timeout(1000)
async def edit_user(self) -> None:
"""编辑用户"""
# 点击修改按钮 (第一行)
row = self.page.locator('tbody tr').first
await row.get_by_role('button').nth(0).click()
dialog = self.page.get_by_role('dialog')
await dialog.wait_for()
# 修改性别为女
await dialog.locator('div.el-form-item').filter(has_text='用户性别').locator('.el-select').click()
await self.page.get_by_role('option', name='女').click()
# 确定
await self.page.get_by_role('button', name='确 定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("修改成功")', timeout=10000)
async def change_user_status(self) -> None:
"""修改用户状态"""
# 点击开关 (第一行)
switch = self.page.locator('.el-switch').first
await switch.click()
# 确认对话框
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector('div:has-text("成功")', timeout=5000)
# 再次点击恢复
await self.page.wait_for_timeout(1000)
await switch.click()
await self.page.get_by_role('button', name='确定').click()
await self.wait_for_selector('div:has-text("成功")', timeout=5000)
async def delete_user(self) -> None:
"""删除用户"""
# 点击删除按钮 (第一行第二个按钮)
row = self.page.locator('tbody tr').first
await row.get_by_role('button').nth(1).click()
# 确认删除
await self.page.get_by_role('button', name='确定').click()
# 等待成功提示
await self.wait_for_selector('div:has-text("删除成功")', timeout=10000)
# 重置搜索
await self.page.get_by_role('button', name='重置').click()
await self.page.wait_for_timeout(1000)
async def test_user_crud_operations(self) -> None:
"""测试用户管理增删查改"""
# 访问页面
await self.goto_page(Config.frontend_url + '/system/user')
await self.wait_for_page_title('用户管理', timeout=10000)
# 生成测试数据
data = self.generate_user_data()
# 1. 新增
await self.create_user(data['user_name'], data['nick_name'], data['phone'])
# 验证新增结果
await self.search_user(data['user_name'])
rows = await self.get_table_total_rows()
assert rows >= 1, '新增后搜索应有结果'
# 2. 修改状态
await self.change_user_status()
# 3. 编辑
await self.edit_user()
# 4. 删除
await self.delete_user()
# 验证删除结果
await self.search_user(data['user_name'])
rows_after = await self.get_table_total_rows()
assert rows_after == 0, '删除后搜索应无结果'
@pytest.mark.asyncio
async def test_user_management_page() -> None:
"""测试用户管理页面功能"""
async with async_playwright() as p:
test_instance = UserManagementTest()
await test_instance.setup(p)
try:
await test_instance.test_user_crud_operations()
finally:
await test_instance.teardown()