""" 真实的端到端(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", "phone": f"1380013800{i}", "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()