PayrollManagementSystem/frontend/e2e/settings_flow.py
liuwenxin fc8a42bcd9 test(m3): 集成/e2e 增补三模块用例并更新测试记录
- 新增 e2e:settings_flow(改名→登出→登录页即时生效→finally 还原防污染)、
  secondary_allocation_flow(建客户/员→录两行 30%+40%→编辑回显与合计条
  余额提示→删除清理;宽视口防弹窗遮挡、alloc-table 专属类防选择器错位)
- 修复 e2e 首跑抓出的两处缺陷:① public 端点 300s 响应缓存让登录页拿旧
  公司名(登录页改时间戳 fetch + cache:no-store,手动解包信封)② logo 同
  扩展名覆盖时先写后删(清理移到写入前且仅异扩展名执行)
- 分配表加 alloc-table 专属类(与产品明细 items-table 区分)
- 全量回归 697 passed(基线 625 + 72);e2e 七脚本全过(既有五脚本回归 +
  两个新增);pnpm type-check 零错误
- tests/README.md 增 M3 节(44 例明细)/运行记录/缺陷 3 行;frontend/
  README.md 增 M3 e2e 节

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-16 12:01:52 +08:00

128 lines
4.8 KiB
Python
Raw Permalink 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.

"""公司信息设置端到端验证。
覆盖的链路(只有真实浏览器才能验证的组合):
- 登录 → 系统管理分组出现「公司信息」菜单 → 进入设置页
- 修改公司名称并保存 → 全局提示「已保存」
- 退出登录 → 登录页品牌位显示**新**公司名(public 端点即时生效)
- 还原公司名(finally:防污染 login_flow/system_management 的品牌名断言)
运行前置(缺一不可):
docker compose up -d # MySQL + Redis
cd backend && uv run uvicorn app.main:app --port 8000
cd frontend && pnpm dev
cd frontend && E2E_ADMIN_PASSWORD=<开发库admin密码> python e2e/settings_flow.py
"""
import os
from playwright.sync_api import Page, sync_playwright
from common import (
BASE_URL,
E2EFailure,
collect_console_errors,
current_path,
expect_toast,
fail,
login,
open_menu,
)
ADMIN = "admin"
console_errors: list[str] = []
def step_change_company_name(page: Page, admin_password: str) -> str:
"""进设置页改公司名(加随机后缀避免撞库),返回新名供后续断言。"""
login(page, ADMIN, admin_password)
open_menu(page, "公司信息", "系统管理")
if current_path(page) != "/system/settings":
fail("菜单导航", f"未进入公司信息页,当前 {page.url}")
# 随机名:单行特性下库可复用,避免与既有公司名撞车;断言不依赖「默认」前提
new_name = f"衍水e2e{os.getpid()}{os.urandom(2).hex()}"
page.fill('input[placeholder="展示在登录页与导出页眉"]', new_name)
page.click("button:has-text('保存')")
expect_toast(page, "已保存", "修改公司名称")
print(f"✓ 1. 设置页导航 进入公司信息并保存新名称「{new_name}」")
return new_name
def step_logout(page: Page) -> None:
page.click("text=退出登录")
page.wait_for_url("**/login**", timeout=10000)
print("✓ 2. 退出登录 已回到登录页")
def step_login_page_reflects(page: Page, new_name: str) -> None:
"""登录页品牌位应显示新公司名(public 端点即时生效)。
等 brand 元素文本变为新名(而非固定 sleep):public 请求是异步的,
固定等待在慢机上会偶发拿到旧值。
"""
page.goto(f"{BASE_URL}/login", wait_until="networkidle")
try:
page.locator(".card-brand__name").filter(has_text=new_name).wait_for(
state="visible", timeout=8000
)
except Exception:
brand = page.locator(".card-brand__name").inner_text().strip()
fail("登录页品牌", f"品牌位未显示新名称「{new_name}」,当前:{brand}")
foot = page.locator(".login-foot").inner_text()
if new_name not in foot:
fail("登录页页脚", f"页脚未含新名称「{new_name}」,当前:{foot}")
print(f"✓ 3. 品牌即时生效 登录页品牌位与页脚已显示「{new_name}」")
def step_restore(page: Page, admin_password: str) -> None:
"""还原默认公司名,防污染后续 e2e 的品牌名断言。"""
login(page, ADMIN, admin_password)
open_menu(page, "公司信息", "系统管理")
page.fill('input[placeholder="展示在登录页与导出页眉"]', "衍水集团")
page.click("button:has-text('保存')")
expect_toast(page, "已保存", "还原公司名称")
print("✓ 4. 数据清理 公司名已还原为「衍水集团」")
def run(page: Page) -> None:
collect_console_errors(page, console_errors)
admin_password = os.environ.get("E2E_ADMIN_PASSWORD", "")
if not admin_password:
fail("环境变量", "未设置 E2E_ADMIN_PASSWORD(开发库 admin 密码)")
new_name = step_change_company_name(page, admin_password)
try:
step_logout(page)
step_login_page_reflects(page, new_name)
finally:
step_restore(page, admin_password)
if console_errors:
print(f" (控制台错误 {len(console_errors)} 条:{console_errors[:2]})")
def main() -> int:
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page()
page.set_default_timeout(10000)
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())