Files
gym-manage/test-suite/tests/uat/test_uat_user_experience.py

422 lines
15 KiB
Python

"""
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']}登录表单应正常显示"