""" UAT用户体验验收测试 测试范围: 1. 界面友好性验证 2. 操作便捷性验证 3. 错误提示友好性验证 4. 响应时间验收 5. 可访问性验证 """ import pytest import time import asyncio from typing import Dict, Any from playwright.async_api import async_playwright, Page, expect from api.auth_api import AuthAPI from api.user_api import UserAPI from api.role_api import RoleAPI from config.settings import settings @pytest.mark.uat @pytest.mark.user_experience @pytest.mark.asyncio class TestUserExperienceUAT: """用户体验验收测试类""" @pytest.fixture async def browser(self): """浏览器fixture""" 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( viewport={"width": 1920, "height": 1080}, locale="zh-CN" ) 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_page(self, page): """已认证的页面fixture""" await page.goto(f"{settings.FRONTEND_URL}/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") yield page async def test_ue_interface_friendly_login(self, page): """ UE-01: 登录界面友好性验证 验证点: - 登录页面布局合理 - 输入框提示清晰 - 按钮位置合理 - 错误提示友好 """ await page.goto(f"{settings.FRONTEND_URL}/login") await page.wait_for_load_state("networkidle") username_input = await page.query_selector('input[placeholder="请输入用户名"]') assert username_input is not None, "用户名输入框应存在" password_input = await page.query_selector('input[placeholder="请输入密码"]') assert password_input is not None, "密码输入框应存在" submit_button = await page.query_selector('button[type="submit"]') assert submit_button is not None, "登录按钮应存在" await page.fill('input[placeholder="请输入用户名"]', "wrong_user") await page.fill('input[placeholder="请输入密码"]', "wrong_pass") await page.click('button[type="submit"]') await asyncio.sleep(1) error_message = await page.query_selector('.el-message--error') assert error_message is not None, "应显示错误提示" error_text = await error_message.text_content() assert len(error_text) > 0, "错误提示应有内容" async def test_ue_interface_friendly_dashboard(self, authenticated_page): """ UE-02: 仪表盘界面友好性验证 验证点: - 页面布局清晰 - 导航菜单易用 - 数据展示直观 - 响应式设计 """ page = authenticated_page sidebar = await page.query_selector('.sidebar-container') assert sidebar is not None, "侧边栏应存在" navbar = await page.query_selector('.navbar') assert navbar is not None, "导航栏应存在" main_content = await page.query_selector('.app-main') assert main_content is not None, "主内容区应存在" await page.set_viewport_size({"width": 768, "height": 1024}) await asyncio.sleep(0.5) mobile_menu = await page.query_selector('.mobile-menu') assert mobile_menu is not None or sidebar is not None, "移动端应显示菜单按钮或侧边栏" async def test_ue_operation_convenience_user_management(self, authenticated_page): """ UE-03: 用户管理操作便捷性验证 验证点: - 列表加载快速 - 搜索功能便捷 - 操作按钮明显 - 表单填写简单 """ page = authenticated_page start_time = time.time() await page.click('text=用户管理') await page.wait_for_load_state("networkidle") load_time = time.time() - start_time assert load_time < 3.0, f"用户管理页面加载时间应小于3秒,实际: {load_time:.2f}秒" search_input = await page.query_selector('input[placeholder*="搜索"]') if search_input: await search_input.fill("admin") await asyncio.sleep(0.5) table_rows = await page.query_selector_all('.el-table__row') assert len(table_rows) > 0, "搜索应返回结果" create_button = await page.query_selector('button:has-text("新增")') assert create_button is not None, "新增按钮应存在且明显" await create_button.click() await page.wait_for_load_state("networkidle") dialog = await page.query_selector('.el-dialog') assert dialog is not None, "新增用户对话框应弹出" form_items = await dialog.query_selector_all('.el-form-item') assert len(form_items) > 0, "表单应包含必填项" async def test_ue_operation_convenience_role_management(self, authenticated_page): """ UE-04: 角色管理操作便捷性验证 验证点: - 角色列表清晰 - 权限树易操作 - 批量操作支持 """ page = authenticated_page await page.click('text=角色管理') await page.wait_for_load_state("networkidle") role_table = await page.query_selector('.el-table') assert role_table is not None, "角色表格应存在" edit_button = await page.query_selector('button:has-text("编辑")') if edit_button: await edit_button.click() await page.wait_for_load_state("networkidle") permission_tree = await page.query_selector('.el-tree') assert permission_tree is not None, "权限树应存在" tree_checkboxes = await permission_tree.query_selector_all('.el-checkbox') assert len(tree_checkboxes) > 0, "权限树应包含可选项" async def test_ue_error_message_friendly(self, page): """ UE-05: 错误提示友好性验证 验证点: - 错误信息清晰 - 错误位置明确 - 解决建议提供 """ await page.goto(f"{settings.FRONTEND_URL}/login") await page.wait_for_load_state("networkidle") await page.click('button[type="submit"]') await asyncio.sleep(1) validation_errors = await page.query_selector_all('.el-form-item__error') assert len(validation_errors) > 0, "应显示表单验证错误" for error in validation_errors: error_text = await error.text_content() assert len(error_text) > 0, "错误信息应有内容" assert "请" in error_text or "不能为空" in error_text, "错误信息应友好" async def test_ue_response_time_acceptance(self, authenticated_page): """ UE-06: 响应时间验收 验证点: - 页面加载时间 < 3秒 - API响应时间 < 1秒 - 列表查询时间 < 2秒 """ page = authenticated_page pages_to_test = [ ("用户管理", "用户管理页面"), ("角色管理", "角色管理页面"), ("菜单管理", "菜单管理页面") ] for menu_text, page_name in pages_to_test: start_time = time.time() await page.click(f'text={menu_text}') await page.wait_for_load_state("networkidle") load_time = time.time() - start_time assert load_time < 3.0, f"{page_name}加载时间应小于3秒,实际: {load_time:.2f}秒" async def test_ue_accessibility_verification(self, authenticated_page): """ UE-07: 可访问性验证 验证点: - 键盘导航支持 - ARIA标签存在 - 对比度合理 - 字体大小合适 """ page = authenticated_page await page.keyboard.press('Tab') await asyncio.sleep(0.2) focused_element = await page.query_selector(':focus') assert focused_element is not None, "应支持键盘导航" buttons = await page.query_selector_all('button') for button in buttons[:5]: aria_label = await button.get_attribute('aria-label') text_content = await button.text_content() assert aria_label or text_content, "按钮应有ARIA标签或文本内容" body = await page.query_selector('body') font_size = await body.evaluate('el => window.getComputedStyle(el).fontSize') font_size_value = float(font_size.replace('px', '')) assert font_size_value >= 14, f"字体大小应不小于14px,实际: {font_size_value}px" async def test_ue_form_validation_feedback(self, authenticated_page): """ UE-08: 表单验证反馈验证 验证点: - 实时验证反馈 - 验证规则清晰 - 错误位置标记 """ page = authenticated_page await page.click('text=用户管理') await page.wait_for_load_state("networkidle") create_button = await page.query_selector('button:has-text("新增")') await create_button.click() await page.wait_for_load_state("networkidle") username_input = await page.query_selector('input[placeholder*="用户名"]') if username_input: await username_input.fill("a") await username_input.blur() await asyncio.sleep(0.5) error_message = await page.query_selector('.el-form-item__error') if error_message: error_text = await error_message.text_content() assert len(error_text) > 0, "应显示验证错误信息" async def test_ue_loading_state_feedback(self, authenticated_page): """ UE-09: 加载状态反馈验证 验证点: - 加载动画显示 - 加载提示清晰 - 禁用重复提交 """ page = authenticated_page await page.click('text=用户管理') await page.wait_for_load_state("networkidle") loading_overlay = await page.query_selector('.el-loading-mask') create_button = await page.query_selector('button:has-text("新增")') if create_button: is_disabled = await create_button.is_disabled() assert not is_disabled, "按钮应可点击" async def test_ue_confirmation_dialog(self, authenticated_page): """ UE-10: 确认对话框验证 验证点: - 危险操作有确认 - 确认信息清晰 - 取消操作支持 """ page = authenticated_page await page.click('text=用户管理') await page.wait_for_load_state("networkidle") delete_buttons = await page.query_selector_all('button:has-text("删除")') if len(delete_buttons) > 0: await delete_buttons[0].click() await asyncio.sleep(0.5) confirm_dialog = await page.query_selector('.el-message-box') assert confirm_dialog is not None, "删除操作应弹出确认对话框" cancel_button = await confirm_dialog.query_selector('button:has-text("取消")') assert cancel_button is not None, "确认对话框应有取消按钮" await cancel_button.click() await asyncio.sleep(0.5) dialog_closed = await page.query_selector('.el-message-box') assert dialog_closed is None, "点击取消应关闭对话框" @pytest.mark.uat @pytest.mark.user_experience @pytest.mark.regression class TestUserExperienceRegression: """用户体验回归测试""" @pytest.fixture async def browser(self): 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): context = await browser.new_context(viewport={"width": 1920, "height": 1080}) yield context await context.close() @pytest.fixture async def page(self, context): page = await context.new_page() page.set_default_timeout(30000) yield page await page.close() @pytest.mark.asyncio async def test_ue_browser_compatibility(self, page): """ UE-REG-01: 浏览器兼容性验证 验证点: - Chrome浏览器兼容 - 主要功能正常 """ await page.goto(f"{settings.FRONTEND_URL}/login") await page.wait_for_load_state("networkidle") login_form = await page.query_selector('.login-form') assert login_form is not None, "登录表单应正常显示" @pytest.mark.asyncio async def test_ue_responsive_design(self, page): """ UE-REG-02: 响应式设计验证 验证点: - 桌面端显示正常 - 平板端显示正常 - 移动端显示正常 """ viewports = [ {"width": 1920, "height": 1080, "name": "桌面端"}, {"width": 768, "height": 1024, "name": "平板端"}, {"width": 375, "height": 667, "name": "移动端"} ] for viewport in viewports: await page.set_viewport_size({"width": viewport["width"], "height": viewport["height"]}) await page.goto(f"{settings.FRONTEND_URL}/login") await page.wait_for_load_state("networkidle") login_form = await page.query_selector('.login-form') assert login_form is not None, f"{viewport['name']}登录表单应正常显示"