Files
gongxue-base/scripts/browser-use-tests.py
wangziqi 42d3f0e27f feat: DingTalk attendance import + integration config + expense types + UI polish
Server:
- Add DingTalk attendance import service with SSE progress streaming
- Add IntegrationConfig entity & module for multi-tenant DingTalk setup
- Add ExpenseType entity & ExpenseTypesModule
- Add SeedModule for DB initialization
- Add UserDingMapping entity for DingTalk user linkage
- Attendance service: import flow with dedup & student auto-mapping
- Rooms service: time-range overlap queries
- Sync controller/service: DingTalk integration wiring
- Permission guard: refactor to pure re-export
- Campus scope middleware: tenant-aware filtering

Admin UI:
- Attendance page: import UI with progress & result summary
- All pages: tableStyle/tablePagination standardization
- Login page: responsive styling
- Sensitive data: useViewSensitive hook for masked viewing
- Vite config: path aliases, build optimization
- Test infra: vitest config, test utilities

Docs: PRD DingTalk batch 1 & 2 design docs
2026-07-09 09:11:56 +08:00

183 lines
8.2 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.

#!/usr/bin/env python3
"""
恭学基地管理系统 — 功能联动集成测试
引擎: browser-use (本地 Playwright) + DeepSeek Chat
用法:
export DEEPSEEK_API_KEY=sk-xxxx
# 先启动服务: npm run dev (admin=:3002, server=:3003)
pip install "browser-use>=0.2" playwright
playwright install chromium
python scripts/browser-use-tests.py
说明:
以下用例中的端口/菜单/字段/按钮文案均已核对当前代码库:
- 前端 admin: http://localhost:3002 (vite.config.ts)
- 后端 API : http://localhost:3003/api (main.ts setGlobalPrefix('api'))
- 默认账号 : admin / admin123 (rbac.service.ts 初始化)
未实现的“课程管理 / 选课 / 学费应收”用例已剔除。
"""
import asyncio
import os
import sys
import time
from dataclasses import dataclass, field
try:
from browser_use import Agent, BrowserProfile
from browser_use.llm import ChatDeepSeek
except ImportError as e: # pragma: no cover
print(f"❌ 缺少依赖: {e}\n 请先: pip install 'browser-use>=0.2' && playwright install chromium")
sys.exit(1)
ADMIN = "http://localhost:3002"
API = "http://localhost:3003/api"
# 每个用例的开场白:登录 admin 系统
L = (
f"打开 {ADMIN}/login ,在“用户名”输入框填 admin在“密码”输入框填 admin123"
f"点“登录”按钮。登录成功后会跳转到“数据面板”。接着"
)
@dataclass
class Test:
name: str
task: str
expect: list[str]
TESTS: list[Test] = [
# 1) 学生 → 入住:学生管理在“学员管理”分组;入住登记在“宿舍运营”→“入住管理”
Test(
name="创建学生→入住",
expect=["学生", "入住", "成功"],
task=(
f"{L}在左侧菜单点“学员管理”展开,点“学生管理”。点右上角“添加学生”按钮,"
f"弹窗里“姓名”填“张三测试”“学号”填“GX2026T01”“性别”选“男”点“保存”。"
f"若提示学号重复,改成 GX2026T02 再存。保存成功后,点左侧“宿舍运营”→“入住管理”,"
f"点“入住登记”按钮,“选择学生”搜索并选“张三测试”,“选择宿舍”选任意一个未满的宿舍,"
f"“入住日期”选今天,点“确认入住”。返回: 学生创建是否成功、入住登记是否成功。"
),
),
# 2) 宿舍费用 → 账单:费用录入在“财务管理”;账单周期是日期区间(RangePicker)
Test(
name="宿舍费用→生成账单",
expect=["费用", "账单", "成功"],
task=(
f"{L}点左侧“财务管理”→“费用录入”。默认在“宿舍费用”标签页,点“录入宿舍费用”按钮,"
f"“宿舍”选任意一间,“费用类型”选“水费”,“金额”填 100“账单周期”选本月第一天到最后一天"
f"(开始日期和结束日期都要选),点“确认录入”。然后点左侧“财务管理”→“账单管理”,"
f"点右上角“生成账单”按钮,“账单周期”同样选本月起止两天,点“生成”。等待完成后查看账单列表。"
f"返回: 费用录入是否成功、账单是否生成成功、账单列表大概有几条记录。"
),
),
# 3) 班级创建:在“学员管理”→“班级管理”;班型只有 文化课/专业课/集训营/冲刺营;编码必填
Test(
name="创建班级",
expect=["班级", "创建", "成功"],
task=(
f"{L}点左侧“学员管理”→“班级管理”。点右上角“创建班级”按钮,"
f"“班级名称”填“测试班级-1班”“班级编码”填“TEST-CLS-01”“班型”选“文化课”"
f"“开班日期”选 2026-07-01点弹窗的确定/保存按钮。"
f"返回: 班级是否创建成功,列表中能否看到“测试班级-1班”。"
),
),
# 4) 排课:注意——没有“新建排课”按钮,需点网格中某个空白单元格弹出新增表单
Test(
name="课程排课",
expect=["排课", "成功"],
task=(
f"{L}点左侧“教务管理”→“排课管理”。页面是“教室 × 星期”的表格。"
f"在“周一”这一列,找任意一间教室对应的空白单元格(显示“—”)并点击它,弹出“新增排课”表单。"
f"“班级”选任意一个班级,“科目”填“语文”,“上课时段”选 08:00 到 10:00"
f"“日期范围”选 2026-07-01 到 2026-12-31点“创建”。"
f"若提示需要先有教室或班级,请如实说明。返回: 排课是否创建成功。"
),
),
# 5) 仪表盘数据一致性
Test(
name="仪表盘数据校验",
expect=["宿舍总数", "在读学生", "入住率"],
task=(
f"{L}停留在“数据面板”,等页面加载完。读出顶部这几个数字指标: "
f"“宿舍总数”、“在读学生”、“当前在住”、“入住率”。然后点“学员管理”→“学生管理”,"
f"记录学生列表底部“共 N 人”的数字;再点“宿舍运营”→“入住管理”,记录“共 N 条”的数字。"
f"返回: 仪表盘的“在读学生”与学生列表条数是否一致、“当前在住”与在住入住记录数是否吻合。"
),
),
# 6) 权限边界:登录错误、未登录重定向、未授权 API 401
Test(
name="权限边界",
expect=["错误", "登录", "401"],
task=(
f"做 3 个安全检查,逐条返回结果:\n"
f"1. 打开 {ADMIN}/login ,用户名 admin密码 wrongpass点“登录”看是否提示错误(登录失败)。\n"
f"2. 新开标签直接访问 {ADMIN}/dashboard (不登录/清掉 token看是否被重定向回 /login。\n"
f"3. 直接访问 {API}/students (不带登录态),看返回的内容里是否包含 401 或 Unauthorized。"
),
),
]
@dataclass
class Result:
name: str
passed: bool
hit: list[str] = field(default_factory=list)
miss: list[str] = field(default_factory=list)
out: str = ""
sec: float = 0.0
err: str | None = None
async def run_one(t: Test, llm: ChatDeepSeek) -> Result:
start = time.monotonic()
try:
agent = Agent(
task=t.task,
llm=llm,
browser_profile=BrowserProfile(headless=True, window_size={"width": 1440, "height": 900}),
)
history = await agent.run(max_steps=30)
out = history.final_result() or ""
hit = [k for k in t.expect if k in out]
miss = [k for k in t.expect if k not in out]
return Result(t.name, len(miss) == 0, hit, miss, out[:600], time.monotonic() - start)
except Exception as e: # noqa: BLE001
return Result(t.name, False, [], t.expect, "", time.monotonic() - start, str(e))
async def main() -> int:
key = os.environ.get("DEEPSEEK_API_KEY")
if not key:
print("❌ 未设置环境变量 DEEPSEEK_API_KEY")
return 1
bar = "=" * 64
print(f"{bar}\n 恭学基地 集成测试 (browser-use + DeepSeek Chat)\n{bar}")
print(f" 前端 {ADMIN} 后端 {API}\n{bar}")
llm = ChatDeepSeek(model="deepseek-chat", api_key=key)
results: list[Result] = []
for i, t in enumerate(TESTS, 1):
print(f"\n[{i}/{len(TESTS)}] {t.name}")
r = await run_one(t, llm)
results.append(r)
if r.passed:
print(f" ✅ 通过 ({r.sec:.0f}s)")
elif r.err:
print(f" ❌ 异常 ({r.sec:.0f}s): {r.err[:160]}")
else:
print(f" ❌ 失败 ({r.sec:.0f}s) 缺关键词: {','.join(r.miss)}\n 输出: {r.out[:180]}")
passed = sum(1 for r in results if r.passed)
failed = len(results) - passed
print(f"\n{bar}\n{passed} / ❌ {failed} 通过率 {passed / len(results) * 100:.0f}%\n{bar}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(asyncio.run(main()))