PayrollManagementSystem/frontend/e2e/role_home_flow.py
liuwenxin 39a3fa7796 fix(dashboard): 修复普通用户落地页与待办角色过滤
- 新增 router/access.ts 统一准入与落地页(工作台→业务数据→回款),
  守卫/登录回跳/菜单/面包屑复用;普通用户不再被无限重定向
- 待办按角色映射在途审批节点(财务/领导/admin),admin 不代办但可查全部
- 预警排除已终止业务;WarningBlock.total 为全量命中数;回款趋势改日期范围
- 工作台加载请求序号防竞态;KPI 口径说明补经理分成/调整单
- 新增 role_home_flow e2e,更新 system_management 普通用户断言

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 09:45:03 +08:00

142 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""浏览器端到端验证:普通用户落地页与路由守卫回退。
工作台收紧为 admin/finance/leader 后,登录默认页不能硬编码 `/dashboard`
(普通用户会被无限重定向)。本脚本验证:
- 普通用户登录后落业务数据页,不出现「无权访问」提示
- 侧边菜单不出现首页/系统管理/报表统计
- 刷新后仍保持登录态与落地页
- 直连 `/dashboard` 或报表页时回退业务数据页并给出权限提示
运行前置:容器、后端 8000、前端 5173 均在运行;设置 ``E2E_ADMIN_PASSWORD``。
运行::
cd frontend && E2E_ADMIN_PASSWORD=<密码> python e2e/role_home_flow.py
"""
import json
import os
import time
import urllib.error
import urllib.request
import uuid
from playwright.sync_api import Page, sync_playwright
from common import BASE_URL, E2EFailure, current_path, fail, login, toast_text
ADMIN = "admin"
USERNAME = f"e2e_role_{int(time.time())}_{uuid.uuid4().hex[:4]}"
PASSWORD = "E2eRole@2026"
HOME_PATH = "/business/businesses"
def _api(method: str, path: str, token: str, body: dict | None = None) -> dict:
"""带令牌的轻量 API 调用(urllib,避免额外依赖)。"""
request = urllib.request.Request(
f"{BASE_URL}{path}",
method=method,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
data=json.dumps(body).encode() if body is not None else None,
)
try:
with urllib.request.urlopen(request, timeout=20) as response:
payload = json.loads(response.read().decode())
except urllib.error.HTTPError as exc:
detail = exc.read().decode()[:200]
raise E2EFailure(f"API {method} {path} HTTP {exc.code}:{detail}") from exc
if payload.get("code") != 200:
raise E2EFailure(f"API {method} {path} 业务失败:{payload.get('message')}")
return payload["data"]
def _token(page: Page) -> str:
token = page.evaluate("localStorage.getItem('ys_token')")
if not token:
fail("API", "未取到登录令牌")
return token
def _create_user(page: Page) -> dict:
return _api(
"POST",
"/api/v1/system/users",
_token(page),
{
"username": USERNAME,
"password": PASSWORD,
"real_name": "E2E普通用户",
"roles": ["user"],
},
)
def run(page: Page) -> None:
admin_password = os.environ.get("E2E_ADMIN_PASSWORD", "")
if not admin_password:
fail("环境变量", "未设置 E2E_ADMIN_PASSWORD(开发库 admin 密码)")
login(page, ADMIN, admin_password)
user = _create_user(page)
try:
page.evaluate("localStorage.clear()")
login(page, USERNAME, PASSWORD, expected_path=HOME_PATH)
page.wait_for_timeout(500)
print("✓ 1. 普通用户登录 落地业务数据页")
menu_text = page.locator(".app-menu").inner_text()
for forbidden in ("首页", "系统管理", "报表统计"):
if forbidden in menu_text:
fail("菜单权限", f"普通用户看到了「{forbidden}」菜单")
print("✓ 2. 菜单权限 首页/系统管理/报表统计均不可见")
page.reload(wait_until="networkidle")
page.wait_for_timeout(600)
if current_path(page) != HOME_PATH:
fail("刷新恢复", f"刷新后未保持落地页,当前 {page.url}")
print("✓ 3. 刷新恢复 登录态与落地页保持")
for path in ("/dashboard", "/reports/personal-detail"):
page.goto(f"{BASE_URL}{path}", wait_until="networkidle")
page.wait_for_timeout(800)
if current_path(page) != HOME_PATH:
fail("路由守卫", f"直连 {path} 未回退业务数据页,当前 {page.url}")
if "无权访问该页面" not in toast_text(page):
fail("路由守卫", f"直连 {path} 未给出权限提示")
print("✓ 4. 路由守卫 受限页均回退业务数据页并提示")
finally:
# 无论用例是否失败都清理测试账号(重新以 admin 登录取令牌)
page.evaluate("localStorage.clear()")
login(page, ADMIN, admin_password)
_api("DELETE", f"/api/v1/system/users/{user['id']}", _token(page))
def main() -> int:
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.set_default_timeout(15000)
try:
run(page)
except E2EFailure as exc:
page.screenshot(path="e2e/failure.png", full_page=True)
print(f"\n ✗ {exc}")
print(" 已保存截图 e2e/failure.png")
return 1
except Exception as exc: # noqa: BLE001 — 测试脚本需打印完整失败原因
page.screenshot(path="e2e/failure.png", full_page=True)
print(f"\n ✗ 执行中断:{type(exc).__name__}: {exc}")
return 1
finally:
browser.close()
print("\n全部通过。")
return 0
if __name__ == "__main__":
raise SystemExit(main())