1e3dc11d59
feat(test-suite): 新增测试套件模块,包含API测试客户端和测试配置 fix(api): 修复数据库实体和仓库的删除操作返回值 style(api): 统一数据库表名和字段命名 perf(api): 添加缓存注解提升配置查询性能 test(api): 添加H2测试数据库配置支持 chore: 清理旧的测试文件和脚本
484 lines
20 KiB
Python
484 lines
20 KiB
Python
"""
|
|
真实的端到端(E2E)测试 - 使用Playwright测试前后端联通
|
|
"""
|
|
|
|
import pytest
|
|
import time
|
|
from playwright.async_api import async_playwright, Page, Browser, BrowserContext
|
|
from httpx import AsyncClient
|
|
|
|
from config.settings import settings
|
|
|
|
|
|
@pytest.mark.e2e
|
|
@pytest.mark.playwright
|
|
class TestRealE2E:
|
|
"""真实的端到端测试类"""
|
|
|
|
@pytest.fixture
|
|
async def browser(self):
|
|
"""浏览器fixture - headless模式"""
|
|
async with async_playwright() as p:
|
|
browser = await p.chromium.launch(headless=True)
|
|
yield browser
|
|
await browser.close()
|
|
|
|
@pytest.fixture
|
|
async def context(self, browser):
|
|
"""浏览器上下文fixture"""
|
|
context = await browser.new_context()
|
|
yield context
|
|
await context.close()
|
|
|
|
@pytest.fixture
|
|
async def page(self, context):
|
|
"""页面fixture"""
|
|
page = await context.new_page()
|
|
page.set_default_timeout(30000)
|
|
yield page
|
|
await page.close()
|
|
|
|
@pytest.fixture
|
|
async def authenticated_client(self):
|
|
"""已认证的HTTP客户端"""
|
|
async with AsyncClient(base_url=settings.API_BASE_URL) as client:
|
|
response = await client.post(
|
|
"/api/auth/login",
|
|
json={
|
|
"username": settings.TEST_USERNAME,
|
|
"password": settings.TEST_PASSWORD
|
|
}
|
|
)
|
|
assert response.status_code == 200
|
|
token = response.json().get("token")
|
|
client.headers.update({"Authorization": f"Bearer {token}"})
|
|
yield client
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complete_user_lifecycle_e2e(self, page, authenticated_client):
|
|
"""测试完整的用户生命周期 - 前后端联通"""
|
|
timestamp = int(time.time() * 1000)
|
|
username = f"e2e_user_{timestamp}"
|
|
email = f"e2e_{timestamp}@example.com"
|
|
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端创建用户
|
|
await page.click('text=用户管理')
|
|
await page.wait_for_url("**/users")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.click('text=新增用户')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.fill('input[placeholder=""]', username)
|
|
await page.fill('input[placeholder=""]', 'Test123!@#')
|
|
await page.fill('input[placeholder=""]', email)
|
|
await page.fill('input[placeholder=""]', '13800138000')
|
|
|
|
await page.click('button:has-text("确定")')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 通过API验证用户已创建
|
|
response = await authenticated_client.get("/api/users")
|
|
assert response.status_code == 200
|
|
users = response.json()
|
|
user_exists = any(user['username'] == username for user in users)
|
|
assert user_exists, f"User {username} not found in API response"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_role_assignment_e2e(self, page, authenticated_client):
|
|
"""测试角色分配 - 前后端联通"""
|
|
timestamp = int(time.time() * 1000)
|
|
role_name = f"E2E_Role_{timestamp}"
|
|
role_key = f"e2e_role_{timestamp}"
|
|
|
|
# 1. 通过API创建角色
|
|
role_response = await authenticated_client.post(
|
|
"/api/roles",
|
|
json={
|
|
"roleName": role_name,
|
|
"roleKey": role_key,
|
|
"roleSort": 1,
|
|
"status": 1
|
|
}
|
|
)
|
|
assert role_response.status_code == 201
|
|
role_id = role_response.json()["id"]
|
|
|
|
# 2. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 通过前端创建用户
|
|
await page.click('text=用户管理')
|
|
await page.wait_for_url("**/users")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.click('text=新增用户')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
username = f"e2e_user_{timestamp}"
|
|
await page.fill('input[placeholder=""]', username)
|
|
await page.fill('input[placeholder=""]', 'Test123!@#')
|
|
await page.fill('input[placeholder=""]', f"e2e_{timestamp}@example.com")
|
|
|
|
await page.click('button:has-text("确定")')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 4. 通过API获取用户ID并分配角色
|
|
users_response = await authenticated_client.get("/api/users")
|
|
users = users_response.json()
|
|
user = next((u for u in users if u['username'] == username), None)
|
|
assert user is not None
|
|
|
|
await authenticated_client.put(
|
|
f"/api/users/{user['id']}",
|
|
json={"roleId": role_id}
|
|
)
|
|
|
|
# 5. 通过API验证角色分配
|
|
user_response = await authenticated_client.get(f"/api/users/{user['id']}")
|
|
assert user_response.status_code == 200
|
|
user_data = user_response.json()
|
|
assert user_data["roleId"] == role_id
|
|
|
|
# 6. 清理测试数据
|
|
await authenticated_client.delete(f"/api/users/{user['id']}")
|
|
await authenticated_client.delete(f"/api/roles/{role_id}")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_and_navigation_e2e(self, page):
|
|
"""测试登录和导航 - 前后端联通"""
|
|
# 1. 访问登录页面
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
title = await page.title()
|
|
assert "登录" in title or "Login" in title.lower()
|
|
|
|
# 2. 填写登录表单
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
|
|
# 3. 点击登录按钮
|
|
await page.click('button[type="submit"]')
|
|
|
|
# 4. 等待跳转到首页或dashboard
|
|
await page.wait_for_url("**/dashboard", timeout=15000)
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 5. 验证用户信息显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 6. 测试导航到不同页面 - 直接导航到URL(避免菜单可见性问题)
|
|
await page.goto("http://localhost:3002/users")
|
|
await page.wait_for_load_state("networkidle")
|
|
assert "users" in page.url
|
|
|
|
await page.goto("http://localhost:3002/roles")
|
|
await page.wait_for_load_state("networkidle")
|
|
assert "roles" in page.url
|
|
|
|
await page.goto("http://localhost:3002/config")
|
|
await page.wait_for_load_state("networkidle")
|
|
assert "config" in page.url
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_system_config_e2e(self, page, authenticated_client):
|
|
"""测试系统配置 - 前后端联通"""
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问系统配置
|
|
await page.click('text=系统配置')
|
|
await page.wait_for_url("**/config")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 验证配置列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 4. 通过API获取配置
|
|
config_response = await authenticated_client.get("/api/config")
|
|
assert config_response.status_code == 200
|
|
configs = config_response.json()
|
|
|
|
# 5. 验证前后端数据一致
|
|
page_content = await page.content()
|
|
for config in configs[:3]:
|
|
assert config['configKey'] in page_content or config['configName'] in page_content
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_search_and_filter_e2e(self, page, authenticated_client):
|
|
"""测试搜索和过滤 - 前后端联通"""
|
|
timestamp = int(time.time() * 1000)
|
|
|
|
# 1. 通过API创建多个测试用户
|
|
user_ids = []
|
|
for i in range(3):
|
|
username = f"search_{timestamp}_{i}"
|
|
response = await authenticated_client.post(
|
|
"/api/users",
|
|
json={
|
|
"username": username,
|
|
"password": "Test123!@#",
|
|
"email": f"search_{timestamp}_{i}@example.com",
|
|
"status": 1
|
|
}
|
|
)
|
|
assert response.status_code == 201
|
|
user_ids.append(response.json()["id"])
|
|
|
|
try:
|
|
# 2. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 通过前端搜索用户
|
|
await page.click('text=用户管理')
|
|
await page.wait_for_url("**/users")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.fill('input[placeholder="搜索用户名或邮箱"]', f"search_{timestamp}")
|
|
await page.click('button:has-text("搜索")')
|
|
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 4. 验证搜索结果显示
|
|
page_content = await page.content()
|
|
assert f"search_{timestamp}" in page_content
|
|
|
|
# 5. 通过API验证搜索结果
|
|
search_response = await authenticated_client.get(
|
|
"/api/users/page",
|
|
params={"keyword": f"search_{timestamp}", "page": 0, "size": 10}
|
|
)
|
|
assert search_response.status_code == 200
|
|
search_data = search_response.json()
|
|
assert len(search_data["content"]) >= 3
|
|
|
|
finally:
|
|
# 6. 清理测试数据
|
|
for user_id in user_ids:
|
|
try:
|
|
await authenticated_client.delete(f"/api/users/{user_id}")
|
|
except Exception:
|
|
pass
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_role_management_e2e(self, page, authenticated_client):
|
|
"""测试角色管理 - 前后端联通"""
|
|
timestamp = int(time.time() * 1000)
|
|
role_name = f"Role_{timestamp}"
|
|
role_key = f"role_{timestamp}"
|
|
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问角色管理
|
|
await page.click('text=角色管理')
|
|
await page.wait_for_url("**/roles")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 通过前端创建角色
|
|
await page.click('text=新增角色')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.fill('input[placeholder=""]', role_name)
|
|
await page.fill('input[placeholder=""]', role_key)
|
|
|
|
await page.click('button:has-text("确定")')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 4. 通过API验证角色已创建
|
|
roles_response = await authenticated_client.get("/api/roles")
|
|
assert roles_response.status_code == 200
|
|
roles = roles_response.json()
|
|
role_exists = any(r['roleName'] == role_name for r in roles)
|
|
assert role_exists, f"Role {role_name} not found in API response"
|
|
|
|
# 5. 清理测试数据
|
|
role_id = next((r['id'] for r in roles if r['roleName'] == role_name), None)
|
|
if role_id:
|
|
await authenticated_client.delete(f"/api/roles/{role_id}")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_menu_management_e2e(self, page, authenticated_client):
|
|
"""测试菜单管理 - 前后端联通"""
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问菜单管理
|
|
await page.click('text=菜单管理')
|
|
await page.wait_for_url("**/menus")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 验证菜单列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 4. 通过API获取菜单
|
|
menus_response = await authenticated_client.get("/api/menus")
|
|
assert menus_response.status_code == 200
|
|
menus = menus_response.json()
|
|
assert len(menus) > 0, "No menus found"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dict_management_e2e(self, page, authenticated_client):
|
|
"""测试字典管理 - 前后端联通"""
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问字典管理
|
|
await page.click('text=字典管理')
|
|
await page.wait_for_url("**/dicts")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 验证字典列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 4. 通过API获取字典
|
|
dicts_response = await authenticated_client.get("/api/dict/types")
|
|
assert dicts_response.status_code == 200
|
|
dicts = dicts_response.json()
|
|
assert len(dicts) > 0, "No dictionaries found"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notice_management_e2e(self, page, authenticated_client):
|
|
"""测试通知管理 - 前后端联通"""
|
|
timestamp = int(time.time() * 1000)
|
|
notice_title = f"通知_{timestamp}"
|
|
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问通知管理
|
|
await page.click('text=通知管理')
|
|
await page.wait_for_url("**/notices")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 通过前端创建通知
|
|
await page.click('text=新增通知')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
await page.fill('input[placeholder=""]', notice_title)
|
|
await page.fill('textarea[placeholder=""]', '测试通知内容')
|
|
|
|
await page.click('button:has-text("确定")')
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 4. 通过API验证通知已创建
|
|
notices_response = await authenticated_client.get("/api/notices")
|
|
assert notices_response.status_code == 200
|
|
notices = notices_response.json()
|
|
notice_exists = any(n['title'] == notice_title for n in notices)
|
|
assert notice_exists, f"Notice {notice_title} not found in API response"
|
|
|
|
# 5. 清理测试数据
|
|
notice_id = next((n['id'] for n in notices if n['title'] == notice_title), None)
|
|
if notice_id:
|
|
await authenticated_client.delete(f"/api/notices/{notice_id}")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_file_management_e2e(self, page, authenticated_client):
|
|
"""测试文件管理 - 前后端联通"""
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问文件管理
|
|
await page.click('text=文件管理')
|
|
await page.wait_for_url("**/files")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 验证文件列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 4. 通过API获取文件列表
|
|
files_response = await authenticated_client.get("/api/files")
|
|
assert files_response.status_code == 200
|
|
files = files_response.json()
|
|
# 文件列表可能为空,但API应该正常返回
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_audit_log_e2e(self, page, authenticated_client):
|
|
"""测试审计日志 - 前后端联通"""
|
|
# 1. 通过前端登录
|
|
await page.goto("http://localhost:3002/login")
|
|
await page.fill('input[placeholder="请输入用户名"]', settings.TEST_USERNAME)
|
|
await page.fill('input[placeholder="请输入密码"]', settings.TEST_PASSWORD)
|
|
await page.click('button[type="submit"]')
|
|
await page.wait_for_url("**/")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 2. 通过前端访问操作日志
|
|
await page.click('text=操作日志')
|
|
await page.wait_for_url("**/operation-logs")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 3. 验证操作日志列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 4. 通过API获取操作日志
|
|
logs_response = await authenticated_client.get("/api/audit/operation-logs")
|
|
assert logs_response.status_code == 200
|
|
logs = logs_response.json()
|
|
|
|
# 5. 通过前端访问登录日志
|
|
await page.click('text=登录日志')
|
|
await page.wait_for_url("**/login-logs")
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
# 6. 验证登录日志列表显示
|
|
await page.wait_for_selector('.el-card', timeout=10000)
|
|
|
|
# 7. 通过API获取登录日志
|
|
login_logs_response = await authenticated_client.get("/api/audit/login-logs")
|
|
assert login_logs_response.status_code == 200
|
|
login_logs = login_logs_response.json()
|