【用 Vibe Coding 实现的 C++17 在线判题系统】前端开发 + Web 自动化测试

文章目录

  • 前言
  • 四、前端开发
    • [1. 安装 ui ux pro max skill(强化AI的前端能力,开发出视觉效果更好的页面)](#1. 安装 ui ux pro max skill(强化AI的前端能力,开发出视觉效果更好的页面))
    • [2. 阶段 6:前端骨架(完成 index.html首页 的开发)](#2. 阶段 6:前端骨架(完成 index.html首页 的开发))
    • [3. 开发登录&注册页面](#3. 开发登录&注册页面)
    • [4. 开发题目列表页](#4. 开发题目列表页)
    • [5. 开发题目详情页](#5. 开发题目详情页)
    • [6. 开发提交记录页](#6. 开发提交记录页)
    • [7. 开发管理页面(管理员进行 CURD 题目的操作)](#7. 开发管理页面(管理员进行 CURD 题目的操作))
    • [8. 未登录用户 访问提交记录页受限(普通成员 访问管理页面受限)](#8. 未登录用户 访问提交记录页受限(普通成员 访问管理页面受限))
  • [五、Web 自动化测试](#五、Web 自动化测试)
    • [1. 认识 playwright-cli(MCP 和 CLI的区别)](#1. 认识 playwright-cli(MCP 和 CLI的区别))
    • [2. 搭建 playwright-cli 环境 (Windows)](#2. 搭建 playwright-cli 环境 (Windows))
      • [2.1 安装 node.js](#2.1 安装 node.js)
      • [2.2 安装 opencode](#2.2 安装 opencode)
      • [2.3 确认浏览器版本](#2.3 确认浏览器版本)
      • [2.4 安装 playwright-cli(含简单的使用介绍)](#2.4 安装 playwright-cli(含简单的使用介绍))
      • [2.5 安装 playwright-cli skill](#2.5 安装 playwright-cli skill)
    • [3. 生成 Web 自动化测试报告(用linux上的opencode生成)](#3. 生成 Web 自动化测试报告(用linux上的opencode生成))
    • [4. 构造测试数据(重置数据库中的数据 的工具,不对外暴露)](#4. 构造测试数据(重置数据库中的数据 的工具,不对外暴露))
    • [5. linux端启动服务器,windows端进行 Web 自动化测试](#5. linux端启动服务器,windows端进行 Web 自动化测试)
      • [5.1 准备阶段(重置数据库中数据;启动服务器)](#5.1 准备阶段(重置数据库中数据;启动服务器))
      • [5.2 公共布局与导航测试](#5.2 公共布局与导航测试)
      • [5.3 首页与未登录浏览 和 注册 / 登录 / 注销 UI 测试](#5.3 首页与未登录浏览 和 注册 / 登录 / 注销 UI 测试)
      • [5.4 题目列表页 测试](#5.4 题目列表页 测试)
      • [5.5 题目详情页(含编辑器与提交) 测试](#5.5 题目详情页(含编辑器与提交) 测试)
      • [5.6 提交记录与提交详情页 测试](#5.6 提交记录与提交详情页 测试)
      • [5.7 判题状态展示 测试](#5.7 判题状态展示 测试)
      • [5.7 管理员后台(CRUD) 测试](#5.7 管理员后台(CRUD) 测试)
      • [5.8 RBAC 与受限页跳转 + 端到端业务流程 + 非功能 / 兼容性 / 异常 UI 测试](#5.8 RBAC 与受限页跳转 + 端到端业务流程 + 非功能 / 兼容性 / 异常 UI 测试)
      • [5.9 总结(生成 web自动化测试文档-playwright-cli.md)](#5.9 总结(生成 web自动化测试文档-playwright-cli.md))
  • 六、文档编写
    • [1. 编写项目 README 和 DEPOLY](#1. 编写项目 README 和 DEPOLY)

前言

项目的gitee链接:https://gitee.com/bacht/oj_-platform-based_on-vibecoding


四、前端开发

1. 安装 ui ux pro max skill(强化AI的前端能力,开发出视觉效果更好的页面)

发展到现在,LLM 开发网页前端的能力已经很强了,已经可以很容易的开发出视觉效果非常棒的页面了。

但是我们仍然可以通过专业的 skill 对 LLM 的能力进行强化,使他能开发出更专业的网页。

什么是 skill ?
skill 是对 LLM 能力的进⼀步规范和扩展,本质上是⼀组 "提示词" + "示例代码/数据"。
LLM 脑子里已经存在很多很多知识了,都 "学杂了",skill 就是告诉 LLM ,在某个指定场景中,使用哪个知识技能,是 "更好的选择"

能够辅助 LLM 进行前端开发的 skill 有很多,比如 frontend-design , user-interface-designer , sketch-implement-design 等。

我们此处选用⼀个社区热度很高的 ui ux pro max skill 。

bash 复制代码
sudo npm install -g uipro-cli # 这里相当于是安装一个"安装器"

cd /home/ubuntu/project/oj_-platform-based_on-vibecoding # 进⼊项⽬路径(自己的)

uipro init --ai opencode # 以opencode(所使用的AI开发工具)支持的形式安装 skill
bash 复制代码
# 安装一个"安装器"
ubuntu@VM-0-4-ubuntu:~/project/oj_-platform-based_on-vibecoding$ sudo npm install -g uipro-cli

added 23 packages in 2s

17 packages are looking for funding
  run `npm fund` for details
  
# 以opencode(所使用的AI开发工具)支持的形式安装 skill
ubuntu@VM-0-4-ubuntu:~/project/oj_-platform-based_on-vibecoding$ uipro init --ai opencode

UI/UX Pro Max Installer

info Installing for: OpenCode (.opencode/skills/)
✔ Generated from templates!

info Installed folders:
  + .opencode

success UI/UX Pro Max installed successfully!

Next steps:
  1. Restart your AI coding assistant
  2. Try: "Build a landing page for a SaaS product"
  • 安装效果:
  • opencode 如何使用该 skill(安装好skill后,需要重启opencode,才能看到新装的skill)

在opencode中,输入以下命令(查看当前可用的skill):

bash 复制代码
/skills

选择 ui-ux-pro-max,然后再输入问题(这次AI的回答就会参考skill)

2. 阶段 6:前端骨架(完成 index.html首页 的开发)

  • 提示词:
bash 复制代码
/ui-ux-pro-max @SPEC.md 请根据⽂档中的描述, 完成 阶段 6:前端骨架(MPA) 的开发工作,其中 index.html(首页)采用深色主题,科技风格。完成后将该步骤标记为完成状态

(1)阶段 6 完成。总结:

  • T6.1 后端 HTML 路由 --- oj-server/src/server/html_serve.{h,cpp}

    • 映射 SPEC §7.1 全部 10 条 MPA 路径 → web/*.html
    • 兜底 catch-all / 处理未命中路径 → 漂亮 404 页(带设计系统 token)
    • RegisterHtmlRoutes 在 main.cpp 中先于 RegisterRoutes 注册,cpp-httplib first-match-wins
    • banner 升级到 phase 6 (... + html routes)
  • T6.2 首页(深色 + 科技风) --- web/index.html + web/static/css/{base,components,pages}.css

    • 设计令牌:bg #020617 · text #F8FAFC · CTA #22C55E · accent #22D3EE
    • 字体:Space Grotesk (display) + DM Sans (body) + JetBrains Mono (terminal)
    • 布局:Hero(含终端代码片段 + 脉冲 LED + 语法高亮)→ Bento Grid(feature/tall/wide 三种跨度)→ CTA strip
    • 流动态徽章 / 渐变品牌 mark / prefers-reduced-motion 兼容
  • T6.3-T6.6 共享 JS --- web/static/js/

    • api.js --- ApiError 类 + 13 个 REST 端点(auth/problems/submissions/admin) + credentials: 'include'
    • store.js --- 用户缓存 + dedupe in-flight + toast 系统(success/error/info)
    • components.js --- navbar/footer 注入 + highlightActive(route) + 移动端菜单 + 登出流程
    • editor.js --- ace.js 懒加载 + C++ mode + textarea fallback
  • T6.7 partials --- web/static/partials/{navbar,footer}.html(components.js 优先 fetch partial,失败则用内联兜底)

    验证

    • 所有 10 条 MPA 路由 → 200 + text/html
    • 全部 /static/* 资源 → 200
    • /api/* JSON 端点未受影响(health/problems/auth/me 全部正常)
    • 404 路径 → 设计过的 404 页
    • ctest 20/20 通过(无回归)

阶段 7 (各页面具体表单/列表/编辑器逻辑) 的脚手架已就位,每页都引用了相同的 partial + design system。

(2)开发完毕后, index.html首页 的⻚⾯效果如下:

3. 开发登录&注册页面

  • 提示词:
bash 复制代码
/ui-ux-pro-max @SPEC.md 请开发 Phase 7 这⾥的"`login.html` + `register.html` + `pages/login.js` + `pages/register.js`(登录&注册⻚⾯)"工作。完成后将该步骤标记为完成状态
  • 代码实现:

    • oj-server/web/login.html (3.8KB) --- 双栏布局:左品牌面板 / 右表单卡片(用户名 + 密码 + 提交 + 错误区 + 注册跳转链接);手机端左面板自动隐藏
    • oj-server/web/register.html (4.4KB) --- 同布局;用户名 + 密码 + 确认密码
    • oj-server/web/static/js/pages/login.js (5.9KB) --- 客户端校验(用户名 3-20 A-Za-z0-9_-.、密码 ≥6)+ api.login → store.refreshUser → 重定向 ?next=(同源路径白名单防 open-redirect,路径以 // 或 /\ 开头的直接丢弃)
    • oj-server/web/static/js/pages/register.js (5.6KB) --- 同源校验 + 确认密码一致性 + api.register → 自动 api.login → toast「账号创建成功」→ 跳转
    • oj-server/web/static/css/pages.css 末尾追加 20 条 .auth-page* 规则(左右栅格 + 品牌面板渐变 + 响应式断点 ≤820px)
    • SPEC.md 行 921 标记 x
  • 关键 UX/安全细节

    • 已登录访问 /login → window.location.replace(?next=) 直跳,不渲染表单
    • 401 → "用户名或密码错误"(统一,不区分账号 / 密码是否错)
    • 409 → "用户名已被占用"
    • 错误码 invalid_input / invalid_credentials 分别走对应文案,5xx 走 fallback
    • safeNext() 拒绝 //evil.com / /\evil.com 等协议相对路径,redirect 到默认 /problems
    • 提交期间按钮 disable + 文案"登录中.../注册中...",完成后焦点自动回到密码框

(1)开发完毕后, 登录&注册页面效果如下:

4. 开发题目列表页

  • 提示词:
bash 复制代码
@SPEC.md 请开发 Phase 7 这⾥的"`problems.html` + `pages/problems.js`(分页 + 难度/标签筛选)(题⽬列表⻚)"工作。完成后将该步骤标记为完成状态
  • 代码实现:

    • oj-server/web/problems.html (5.0KB) --- 题库页骨架:page-head + 筛选条(标签搜索 / 难度下拉 / 每页数量 / 清除筛选)+ meta 行 + list + 空态 + 错误态 + 分页条 + 管理员快捷入口
    • oj-server/web/static/js/pages/problems.js (15.6KB) --- URL ↔ 状态双向同步、AbortController 取消旧请求、debounce 标签搜索、Enter 立即提交、Esc 还原、空/错/加载骨架三分态、windowed 分页(始终含首末页 + 当前 ± 2 + 省略号)、popstate 反向导航
    • oj-server/web/static/css/pages.css 追加 195 行:.problems-filters 响应式栅格、.problems-meta 元信息行、.problems-empty / .problems-error 状态卡、.pagination + .pagination__item.is-current + .pagination__ellipsis、.skeleton-line 呼吸光动画、.problem-row--skeleton 禁用悬停与点击
  • 核心设计

    • URL 即状态:?page=&page_size=&difficulty=&tag= 完整反映 UI,所有筛选/翻页都可分享、可收藏、回退
    • 筛选 → replaceState:难度/标签/每页变化不污染历史栈,避免每个键入字符都成一个条目
    • 翻页 → pushState:浏览器后退按钮能返回上一页,符合 MPA 直觉
    • popstate 监听:浏览器前进/后退也能正确同步 UI 并重新拉数据
    • 请求竞态防护:AbortController 在每次新请求前取消上一次;inflight 引用比较防止过期响应覆盖新数据
    • 服务端是真理:客户端把 ?page=0/-5/非枚举 difficulty/越界 page_size/超长 tag 全部回退到默认值,不发垃圾请求;后端 400 用 inline error 提示
    • 文档标题随页码变化:第 3 页 · OJ Platform 在浏览器 tab 上更清晰
    • 键盘可达:分页按钮、表单控件都有 focus ring;Enter 立即提交标签;Esc 还原标签
    • XSS 防护:所有从 API 来的字符串走 escapeHtml()(

(1)页面效果如下:

5. 开发题目详情页

  • 提示词:
bash 复制代码
@SPEC.md 请开发 Phase 7 这⾥的"`problem.html` + `pages/problem_detail.js`(题目渲染 + ace.js 编辑器 + 提交按钮 + 轮询结果)(题目详情页)"工作。完成后将该步骤标记为完成状态
  • 代码实现:

    • oj-server/web/problem.html 题目详情页骨架(左侧描述/样例/最近提交,右侧 ace 编辑器 + 提交 + 状态栏)
    • oj-server/web/static/js/pages/problem_detail.js URL id 解析、问题渲染、ace 挂载、提交 + 轮询 + 终态、localStorage 持久化、Ctrl+Enter 快捷键
    • oj-server/web/static/css/pages.css 新增 .problem-detail 系列:2 列响应式 grid、.problem-statement/.problem-sample/.problem-latest、.verdict 8 种状态色、.status-spinner
    • oj-server/web/static/vendor/ace/ace.js cdnjs ace.js 1.32.7(429 KB),editor.js 懒加载路径
  • 关键设计要点

    • 状态机:skeleton → main 或 error;提交期 submitInFlight 防重入;轮询期 pollAbort + pollTimer 可取消;轮询 60 s 硬超时(防 placeholder worker 不写终态的悬挂情况)
    • 轮询策略:初始 1 s,第 5 次后切到 2 s;终态(AC/WA/TLE/RE/CE/SE)即停;网络抖动不终止轮询,仅 60 s 后让步并 toast 提示
    • 代码持久化:localStorage"oj:code:",每次输入后 400 ms 防抖;"重置"按钮清掉并回到默认模板;首次访问无缓存时落入模板
    • 认证门控:未登录时提交按钮 disabled + 显示 anon 提示条带 "登录" CTA(带 ?next= 同源回跳);登录后由 store.refreshUser().then() 二次刷新状态
    • 键盘:Ctrl/Cmd + Enter 在文档级监听 → submit(textarea fallback / ace 都生效);acemin.js 加载失败自动降级 textarea,行为不中断
    • 描述渲染:极简 markdown-lite ------ 三反引号块 → < pre >< code >、单反引号 → < code >、空行分段、所有用户内容走 escapeHtml;XSS 安全(node 单测已验证)
    • 最新提交:页面底部展示该用户对此题的最近 5 条 submission,每条带状态 badge,点击跳 /submissions/:id
    • 管理员捷径:data-admin-only 块在 admin 时露出 "编辑题目" → /admin/problems/:id/edit

(1)页面效果如下:

6. 开发提交记录页

  • 提示词:
bash 复制代码
@SPEC.md 请开发 Phase 7 这⾥的"`submissions.html` + `submission.html` + `pages/submissions.js` + `pages/submission_detail.js`(提交记录页)"工作。完成后将该步骤标记为完成状态
  • 代码实现:
文件 行数 作用
oj-server/web/submissions.html 164 列表页骨架:过滤 + meta + 列表/空/错 + 分页 + 匿名门
oj-server/web/submission.html 174 详情页骨架:面包屑 + 状态大卡 + 诊断块 + 元数据 + 代码 + 操作
oj-server/web/static/js/pages/submissions.js 475 列表逻辑:URL ↔ 状态、过滤、offset 分页、inflight 去抖、popstate、匿名门
oj-server/web/static/js/pages/submission_detail.js 418 详情逻辑:URL 解析、polling (1s→2s, 60s 超时)、8 色 verdict、CE/WA 诊断、复制代码 (Clipboard API + textarea 兜底)、状态机 5 态
oj-server/web/static/css/pages.css +470 .submissions-page/-filters/-meta/-list/-empty/-error/-anon/-pagination + .submission-detail/-skeleton/-notfound/-anon/-error/-main/-head/-crumb/-back/-diag/-meta/-metalist/-metarow/-code/-code-head/-code-wrap/-actions + .verdict--detail + .verdict__side/-label/-link + .pagination--offset
  • 关键设计

    • 列表页

      • URL = 唯一真相:?limit=&offset=&problem_id=&status=(与 problems 页一致)
      • 默认 limit=20,可选 10, 20, 50;offset ≥ 0;status ∈ {Pending,Judging,AC,WA,TLE,RE,CE,SE};problem_id ∈ /^\d{1,20}$/
      • 过滤变更 → replaceState + 重置 offset=0;翻页 → pushState;popstate 重新同步
      • API 不返回 total(API.md §4.3 故意),所以用 prev / "第 N 页" / next 三件套;末页检测 = items.length < limit
      • AbortController 单 inflight + identity 比对(防老响应盖新响应)
      • 匿名门:未登录显示锁卡 + 「登录」跳转 /login?next=,不发起列表请求
    • 详情页

      • URL 解析 /submissions/(\d+) → 失败显示错误
      • 5 态状态机:skeleton / notfound / anon / error / main
      • 服务端把"跨用户访问"折叠成 404(API.md §4.2 故意),客户端不泄漏存在性
      • Pending/Judging → 启动 polling(1s,5 次后 2s,60s 强制超时),终态(AC/WA/TLE/RE/CE/SE)立刻停止
      • Verdict 复用 .verdict--ac/wa/tle/re/ce/se/pending/neutral 8 色系统 + .verdict--detail 大卡变体(52px 图标 + 内置侧边题号链接)
      • 诊断块:WA/TLE/RE/CE/SE 时显示 error_msg(CE 含完整 g++ 输出)--- < pre > 等宽保留换行
      • 元数据栅格:6 字段 (id/language/status/problem/created/finished) 双列 → 窄屏单列
      • 复制代码:navigator.clipboard.writeText 优先,textarea + execCommand('copy') 兜底;点击后标签 "复制代码" → "已复制"

(1)页面效果如下:

7. 开发管理页面(管理员进行 CURD 题目的操作)

  • 提示词:
bash 复制代码
@SPEC.md 请开发 Phase 7 这⾥的"T7.5 和 T7.6(开发管理页面)"工作。完成后将该步骤标记为完成状态

(1)页面效果如下:

新增题目功能

删除题目功能

8. 未登录用户 访问提交记录页受限(普通成员 访问管理页面受限)

  • 提示词:
bash 复制代码
@SPEC.md 请开发 Phase 7 这⾥的"全站链接与重定向:未登录访问受限页 → `/login?next=...`;非 admin 访问 `/admin/*` → 403 HTML 页"工作。完成后将该步骤标记为完成状态
  • 服务端 (html_serve.{h,cpp})

    • 新增 RegisterHtmlRoutes(server, web_root, auth, cookie_name) ------ 多两个可选参数:AuthService* + session cookie 名
    • admin_gate 闭包 ------ 三个 /admin/* 路由共用,逻辑:
    • auth==nullptr 或 cookie 名空 → 兜底直接放行 HTML(让 JS gate 接管,测试场景用)
    • anon(cookie 无效/缺失)→ 302 Location: /login?next=<encoded-path?query> + Cache-Control: no-store
    • role=user → 403 + ForbiddenHtml(role)(与 API 端 admin 403 同 chrome)
    • role=admin → 200 + admin HTML
    • EncodeNextParam(req) ------ 把 req.path + req.params 拼成 path?query 并 percent-encode 一遍(cpp-httplib 已经把 query %XX 解码了,所以单次编码就够);正确处理 /admin/problems?q=hello%20world 这种带特殊字符的 URL
    • ReadSessionTokenLocal(req, cookie_name) ------ 本地 cookie 解析(不复用 auth_handlers 的同名函数以避免循环依赖)
  • 客户端 (JS fallback)

    • submissions.js init() ------ 顶部加 anon 早返回:window.location.replace('/login?next=' + encodeURIComponent(pathname+search));保留 applyAuthGate() 的 inline gate 作为 popstate / cookie 中途失效的兜底
    • submission_detail.js init() ------ 同样在顶部加 anon 早返回
    • admin_problems.js gate() ------ 改用 pathname + search 而不是硬编码 /admin/problems,让 /admin/problems/123/edit 被踢去登录后能正确回到原页
    • admin_problem_form.js gate() ------ 已正确,无需改
  • 关键设计决策

    • /admin/* 服务端 gate(spec 字面要求 "返回 403 HTML 页")------ 返回 HTTP 403,浏览器拿到的是真 403 + ForbiddenHtml,而不是 200 + JS 闪一下再跳转;JS gate 留作 cookie 中途失效的兜底
    • /submissions + /submission/:id 客户端 gate ------ 这两个不是高频路径,服务端每次请求都查 user 会浪费 DB 一次;JS gate 在 components.boot() 跑完后立即触发,体感无差别
    • /problems/:id 保持不变 ------ 题目详情页 anon 用户可以浏览(看题面、看样例、看提交历史),只有点「提交」时才被踢到 /login;spec 的 "受限页" 不含这一页
    • admin_gate 闭包化 ------ 三个 /admin/* 路由共用一份逻辑,避免在每个 handler 里复制三分
    • req.params 一次性全编码 ------ cpp-httplib 已把 query %XX decode 成原始字符,只需 enc_unreserved 一遍(之前版本做两次导致 %2520 双重编码,已修)

(1)未登录用户试图访问提交记录页 和 管理页面等,都会直接跳转到登录页面

(2)普通用户试图访问 管理页面,会直接跳转到403页面(显示权限不够)

五、Web 自动化测试

1. 认识 playwright-cli(MCP 和 CLI的区别)

playwright 是微软出品的浏览器自动化测试框架。

就是⼀个 python 的包, 通过 python 代码的方式就可以完成浏览器的自动化测试.

github 仓库:https://github.com/microsoft/playwright

  • 为了让 playwright 能够很好的和 LLM 结合,微软在这个基础上,做出了两种封装:
    • playwright-mcp
    • playwright-cli

(1) 什么是 MCP?

MCP (Model Context Protocol)是一套 "协议规范",目的是给 LLM 安装上 "手"。

LLM 诞生之初,用户的使用方式单纯就是通过 AI 工具,向 LLM 提问,LLM 返回答案。这个过程中,LLM 能听,能说,能思考,但是不能 "做"。

为了让 LLM 也能 "做事",人们就提出了 MCP

  • 用户向 AI 工具描述 "任务是啥"
  • AI 工具把 "任务描述" 和 "当前有哪些 MCP Server " 信息一起告诉给 LLM
  • LLM 结合任务和 MCP Server 的描述, 决定给 MCP Server 发⼀个怎样的请求, 来完成具体的任务. 把这些结果统⼀返回给 AI 工具
  • AI 工具通过 MCP Client 按照 LLM 的返回结果,发送请求给 MCP Server
  • MCP Server 收到请求后,执行具体的任务(比如点击浏览器中的⼀个按钮),并返回响应给 AI 工具.
  • AI 工具把得到的结果再告诉 LLM ,让 LLM 进行分析.
  • LLM 分析完毕,把最终的结论返回给 AI 工具,最终再返回给用户

(2) 什么是 CLI?

CLI 全称为 Command Line Interface, 也就是 "命令行界面"。

我们之前学过的 ls , mkdir , rm 等指令,本质上就是 CLI 程序.
当然, 我们正在使用 opencode 也是 CLI 程序.

通过上面对于 MCP 的分析,我们发现,基于 MCP 的方式执行一个外部的任务,流程非常复杂。这就会导致整个过程消耗大量的 token。

相比之下,同样是给 LLM 安装上 "手",CLI 的方式就轻量很多了

  • 用户向 AI 工具描述 "任务是啥"
  • AI 工具把 "任务描述" 和 "当前有什么 CLI 工具" 信息⼀起告诉给 LLM
  • LLM 结合任务和 CLI 工具信息,生成一个具体的 CLI 命令, 返回给 AI 工具.
  • AI 工具执行这个 CLI 命令, 并把结果返回给 LLM 分析.
  • LLM 把分析结果返回给 AI 工具,再最终返回给用户.

经过各大博主的实测,playwright-cli 相比于 playwright-mcp, token 消耗只有 1/ 4。
在当前的时代,token 就是成本,token 就是效率。因此 playwright-cli 是当前更好的选择。
因此我们后续就会使用 playwright-cli 的⽅式进⾏ web ⾃动化测试。

2. 搭建 playwright-cli 环境 (Windows)

由于我们的浏览器是运行在 windows 上,因此我们需要在 windows 上搭建出 playwright-cli 环境。

2.1 安装 node.js

在 windows 上安装 node.js 非常简单

打开 node.js 官网 https://nodejs.org/zh-cn/download

点击下载 windows 安装程序, 下载后双击安装包安装.

⼀路 next 即可

安装完成后, 确认 node 和 npm 命令可用.

bash 复制代码
C:\Users\30237>node
Welcome to Node.js v24.18.1.
Type ".help" for more information.
>
bash 复制代码
C:\Users\30237>npm
npm <command>

Usage:

npm install        install all the dependencies in your project
npm install <foo>  add the <foo> dependency to your project
npm test           run this project's tests
npm run <foo>      run the script named <foo>
npm <command> -h   quick help on <command>
npm -l             display usage info for all commands
npm help <term>    search for help on <term> (in a browser)
npm help npm       more involved overview (in a browser)

All commands:

    access, adduser, approve-scripts, audit, bugs, cache, ci,
    completion, config, dedupe, deny-scripts, deprecate, diff,
    dist-tag, docs, doctor, edit, exec, explain, explore,
    find-dupes, fund, get, help, help-search, init, install,
    install-ci-test, install-test, link, ll, login, logout, ls,
    org, outdated, owner, pack, ping, pkg, prefix, profile,
    prune, publish, query, rebuild, repo, restart, root, run,
    sbom, search, set, shrinkwrap, stage, star, stars, start,
    stop, team, test, token, trust, undeprecate, uninstall,
    unpublish, unstar, update, version, view, whoami

Specify configs in the ini-formatted file:
    C:\Users\30237\.npmrc
or on the command line via: npm <command> --key=value

More configuration info: npm help config
Configuration fields: npm help 7 config

npm@11.16.0 C:\Program Files\nodejs\node_modules\npm

2.2 安装 opencode

  • 和 Linux 版本的opencode安装方法相同,使用 npm 安装,命令如下:
bash 复制代码
npm i -g opencode-ai
bash 复制代码
C:\Users\30237>npm i -g opencode-ai

added 3 packages in 7m
npm warn allow-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn allow-scripts   opencode-ai@1.18.10 (postinstall: node ./postinstall.mjs)
npm warn allow-scripts
npm warn allow-scripts Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts <pkg>` to allow.
npm notice
npm notice New major version of npm available! 11.16.0 -> 12.0.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.0.2
npm notice To update run: npm install -g npm@12.0.2
npm notice
  • 一段时间过后,opencode安装成功,打开试试:
bash 复制代码
opencode

没问题,opencode正常运行

2.3 确认浏览器版本

虽然 playwright-cli ⽀持多种主流浏览器, 但是我们还是建议使⽤ chrome 最新版本.

确保 chrome 版本更新到最新版, 并设置为默认浏览器.

上⾯谈到的 "主流浏览器" 指的是 chrome, edge, firefox, safari 等.
不包括 360 浏览器, 搜狗浏览器, 猎豹浏览器, 2345 浏览器等⼀众国产浏览器.
这些国产浏览器大多是基于旧版本 (可能是若⼲年前的版本) chrome 内核, 重新包装了⼀套界⾯

2.4 安装 playwright-cli(含简单的使用介绍)

  • playwright-cli 同样使用 npm 安装
bash 复制代码
npm install -g @playwright/cli@latest
bash 复制代码
C:\Users\30237>npm install -g @playwright/cli@latest

added 3 packages in 14s

当前,AI LLM 生态中的,TypeScript / node 在工程化方面占据了很重要的席位.
因此我们看到的很多 AI LLM 相关工具都是基于 TypeScript / node 生态来构建的.

  • 简单的测试:
bash 复制代码
playwright-cli open sogou.com --headed

如果安装正确, 即可顺利开启新的浏览器窗⼝, 并打开搜狗主⻚.

--head 参数非常关键.
如果不加这个参数, 默认会以 "无头" (headless) 的方式运行 chrome , 此时浏览器窗口不可见 (通过隐藏窗口, 降低内存占用, 也防止用户操作产生干扰).
无头模式在生产级环境中更多使用, 但是我们在初学阶段, 还是使用 "有头" 的方式, 更直观的观察到浏览器自动化执行的过程.

  • 通过下列命令可以查看到更完整的参数选项
bash 复制代码
playwright-cli help
bash 复制代码
C:\Users\30237>playwright-cli help
Unknown command: help

Usage: playwright-cli <command> [args] [options]
Usage: playwright-cli -s=<session> <command> [args] [options]

Core:
  open [url]                  open the browser
  attach [name]               attach to a running playwright browser
  close                       close the browser
  detach                      detach from an attached browser
  goto <url>                  navigate to a url
  type <text>                 type text into editable element
  click <target> [button]     perform click on a web page
  dblclick <target> [button]  perform double click on a web page
  fill <target> <text>        fill text into editable element
  drag <startTarget> <endTarget> perform drag and drop between two elements
  drop <target>               drop files or data onto an element
  hover <target>              hover over element on page
  select <target> <val>       select an option in a dropdown
  upload <file>               upload one or multiple files
  check <target>              check a checkbox or radio button
  uncheck <target>            uncheck a checkbox or radio button
  snapshot [target]           capture page snapshot to obtain element ref
  find [text]                 search the page snapshot for text or a regexp, returning matching nodes with surrounding context (like search snippets)
  eval <func> [target]        evaluate javascript expression on page or element
  dialog-accept [prompt]      accept a dialog
  dialog-dismiss              dismiss a dialog
  resize <w> <h>              resize the browser window
  delete-data                 delete session data

Navigation:
  go-back                     go back to the previous page
  go-forward                  go forward to the next page
  reload                      reload the current page

Keyboard:
  press <key>                 press a key on the keyboard, `a`, `arrowleft`
  keydown <key>               press a key down on the keyboard
  keyup <key>                 press a key up on the keyboard

Mouse:
  mousemove <x> <y>           move mouse to a given position
  mousedown [button]          press mouse down
  mouseup [button]            press mouse up
  mousewheel <dx> <dy>        scroll mouse wheel

Save as:
  screenshot [target]         screenshot of the current page or element
  pdf                         save page as pdf

Tabs:
  tab-list                    list all tabs
  tab-new [url]               create a new tab
  tab-close [index]           close a browser tab
  tab-select <index>          select a browser tab

Storage:
  state-load <filename>       loads browser storage (authentication) state from a file
  state-save [filename]       saves the current storage (authentication) state to a file
  cookie-list                 list all cookies (optionally filtered by domain/path)
  cookie-get <name>           get a specific cookie by name
  cookie-set <name> <value>   set a cookie with optional flags
  cookie-delete <name>        delete a specific cookie
  cookie-clear                clear all cookies
  localstorage-list           list all localstorage key-value pairs
  localstorage-get <key>      get a localstorage item by key
  localstorage-set <key> <value> set a localstorage item
  localstorage-delete <key>   delete a localstorage item
  localstorage-clear          clear all localstorage
  sessionstorage-list         list all sessionstorage key-value pairs
  sessionstorage-get <key>    get a sessionstorage item by key
  sessionstorage-set <key> <value> set a sessionstorage item
  sessionstorage-delete <key> delete a sessionstorage item
  sessionstorage-clear        clear all sessionstorage

Network:
  requests                    list all network requests since loading the page. each request is numbered for use with the `request` command.
  request <index>             show full details (headers, body, response) of a single network request by its number from the `requests` command.
  request-headers <index>     print only the request headers for a single network request by its number from the `requests` command.
  request-body <index>        print only the request body for a single network request by its number from the `requests` command.
  response-headers <index>    print only the response headers for a single network request by its number from the `requests` command.
  response-body <index>       print the response body for a single network request by its number from the `requests` command. textual bodies are inlined; binary bodies are saved to a file and the path is printed.
  route <pattern>             mock network requests matching a url pattern
  route-list                  list all active network routes
  unroute [pattern]           remove routes matching a pattern (or all routes)
  network-state-set <state>   set the browser network state to online or offline

DevTools:
  console [min-level]         list console messages
  run-code [code]             run playwright code snippet
  tracing-start               start trace recording
  tracing-stop                stop trace recording
  video-start [filename]      start video recording
  video-stop                  stop video recording
  video-chapter <title>       add a chapter marker to the video recording
  video-show-actions          annotate subsequent cli/mcp actions on the page with a callout that names the action and highlights the target element
  video-hide-actions          stop annotating actions performed on the page
  show                        show playwright dashboard
  pause-at <location>         run the test up to a specific location and pause there
  resume                      resume the test execution
  step-over                   step over the next call in the test
  generate-locator <target>   generate a playwright locator for the given element
  highlight [target]          show (or with --hide, remove) a highlight overlay for an element; `--hide` without a target hides all page highlights.

Install:
  install                     initialize workspace
  install-browser [browser]   install browser

Browser sessions:
  list                        list browser sessions
  close-all                   close all browser sessions
  kill-all                    forcefully kill all browser sessions (for stale/zombie processes)

Global options:
  --help [command]            print help
  --json                      output response as JSON
  --raw                       output only the result value, without status and code
  --version                   print version

注意,不要让这么多的 参数选项 吓住了.
这么多选项不需要我们看,也不需要我们掌握,我们只要让 AI 读取这些内容,理解 playwright-cli 的用法即可。
在 AI 时代,我们需要进行 "思维转换",这些固定的,繁琐的工作, 能丢给 AI 的,都尽量丢给 AI

2.5 安装 playwright-cli skill

为了让 AI 可以更好的理解 playwright-cli 的用法,还可以安装官方提供的 skill.

bash 复制代码
playwright-cli install --skills agents

playwright-cli 相当于⼀个工具箱,里面有很多工具 (螺丝刀,扳手,电钻,膨胀螺丝...)
skill 则相当于是这套工具的 "说明书". (而且这个说明书是专门给 AI 看的)

上面这个命令执行时所在的目录,非常有讲究.

  • 如果是在用户目录中执行 (形如 C:\Users\HGtz2222> ),此时安装的 skill 全局有效.
  • 如果是在当前项目的 目录中执行,此时安装的 skill 只在当前项目中生效

这个命令的本质会在当前目录下 下载对应的 skill 目录, 放到 .agent 目录中.
如果 .agent 是在用户目录下,则全局生效 (主流 AI 工具在各个目录中,均可读取到这个skill)
如果 .agent 是在项目目录下, 则只在当前项目中生效.

此处我安装在用户目录中。效果形如:

bash 复制代码
C:\Users\30237>playwright-cli install --skills agents
✅ Workspace initialized at `C:\Users\30237`.
✅ Skills installed to `.agents\skills\playwright-cli`.
Downloading FFmpeg (playwright ffmpeg v1011) from https://cdn.playwright.dev/dbazure/download/playwright/builds/ffmpeg/1011/ffmpeg-win64.zip
1.3 MiB [====================] 100% 0.0s
FFmpeg (playwright ffmpeg v1011) downloaded to C:\Users\30237\AppData\Local\ms-playwright\ffmpeg-1011
Downloading Winldd (playwright winldd v1007) from https://cdn.playwright.dev/dbazure/download/playwright/builds/winldd/1007/winldd-win64.zip
0.1 MiB [====================] 100% 0.0s
Winldd (playwright winldd v1007) downloaded to C:\Users\30237\AppData\Local\ms-playwright\winldd-1007
✅ Found chrome, will use it as the default browser.

其中的 SKILL.md 就是该 skill 的入口⽂档.

启动 opencode,使用 /skill 命令查看当前的 skill,可以看到 plawright-cli skill 已经安装成功。

注意:启动的是 Windows 上的 opencode

3. 生成 Web 自动化测试报告(用linux上的opencode生成)

回到 Linux 中的 opencode , 让 opencode 生成⼀套 web 自动化的测试流程

  • 提示词:
bash 复制代码
请根据 @SPEC.md 设计⼀套 web ui 测试⽤例, baseurl 为 `http://localhost:8080/`
结果生成到 web_testcase.md 中.
  • 生成的测试用例⽂档(节选,正文1263行)如下:
bash 复制代码
# OJ 平台 Web UI 测试用例

> **BaseURL**: `http://localhost:8080`
> **测试对象**: OJ 仿 LeetCode 平台的全部 Web UI 页面与端到端交互流程
> **测试依据**: `SPEC.md` §3 MVP 功能、§6 API、§7 前端设计 (MPA)、§7.3 关键交互、§11 验收标准
> **架构特征**: 原生 HTML+CSS+JS 多页应用(MPA),真实 URL 路由,无客户端 hash 路由,ace.js 编辑器,500ms 轮询判题状态

---

## 目录

- [1. 测试概述](#1-测试概述)
- [2. 测试用例索引](#2-测试用例索引)
- [3. 公共布局与导航测试](#3-公共布局与导航测试)
- [4. 首页与未登录浏览](#4-首页与未登录浏览)
- [5. 注册 / 登录 / 注销 UI](#5-注册--登录--注销-ui)
- [6. 题目列表页](#6-题目列表页)
- [7. 题目详情页(含编辑器与提交)](#7-题目详情页含编辑器与提交)
- [8. 提交记录与提交详情页](#8-提交记录与提交详情页)
- [9. 判题状态展示](#9-判题状态展示)
- [10. 管理员后台(CRUD)](#10-管理员后台crud)
- [11. RBAC 与受限页跳转](#11-rbac-与受限页跳转)
- [12. 端到端业务流程](#12-端到端业务流程)
- [13. 非功能 / 兼容性 / 异常 UI](#13-非功能--兼容性--异常-ui)

---

## 1. 测试概述

### 1.1 测试范围

| 范围 | 包含 | 不包含 |
|---|---|---|
| 页面 | `/`, `/login`, `/register`, `/problems`, `/problems/:id`, `/submissions`, `/submissions/:id`, `/admin/problems`, `/admin/problems/new`, `/admin/problems/:id/edit` | `/api/*` JSON 接口(由 `基于curl的接口自动化测试文档.md` 覆盖) |
| 交互 | 表单提交、按钮点击、navbar 跳转、筛选分页、ace.js 编辑器、500ms 轮询、状态徽章 | 后端业务逻辑、判题核心算法 |
| 用户角色 | anonymous / user / admin | 其它角色 |

### 1.2 测试环境

- **后端**: 本地 `oj_server`(监听 `8080`),MySQL 已用 `db/init.sql` 初始化(含默认账号 `admin / admin123`)。
- **浏览器**: Chrome (latest) + Firefox (latest);viewport 1280×800、375×812 (mobile sanity)。
- **网络**: localhost,无代理。
- **预置数据**: 至少 3 道题目覆盖 Easy/Medium/Hard,覆盖标签 `array` 和 `graph` 各至少 1 道。
- **预置账号**:
  - `admin / admin123`(role=admin,db/init.sql 默认)
  - `alice / alice123`(role=user,测试用普通用户)
  - `bob / bob123`(role=user,测试用另一普通用户,用于跨用户越权验证)

### 1.3 用例编号约定

| 前缀 | 含义 |
|---|---|
| `UI-COM-*` | 公共布局 / 导航 / 静态资源 |
| `UI-REG-*` | 注册页 |
| `UI-LOG-*` | 登录页 |
| `UI-LOGOUT-*` | 注销 |
| `UI-LIST-*` | 题目列表页 |
| `UI-DETAIL-*` | 题目详情页 |
| `UI-EDIT-*` | 代码编辑器(ace.js) |
| `UI-SUB-*` | 提交动作与轮询 |
| `UI-HIST-*` | 提交记录列表页 |
| `UI-SUB-DETAIL-*` | 提交详情页 |
| `UI-VERDICT-*` | 判题结果展示(状态徽章、错误信息) |
| `UI-ADMIN-LIST-*` | 管理员题目列表页 |
| `UI-ADMIN-NEW-*` | 管理员新建题目页 |
| `UI-ADMIN-EDIT-*` | 管理员编辑题目页 |
| `UI-ADMIN-DEL-*` | 管理员删除题目 |
| `UI-RBAC-*` | 受限页跳转、403 页 |
| `UI-FLOW-*` | 端到端业务流程 |
| `UI-NF-*` | 非功能 / 兼容性 |

**优先级**: `P0` = 验收必过(对应 SPEC §11.1 验收项);`P1` = 主流程相关;`P2` = 体验/边界。

### 1.4 验收项映射

| 验收项 | 对应用例 |
|---|---|
| AUTH-1 | UI-REG-001 ~ UI-REG-006 |
| AUTH-2 | UI-LOG-001 ~ UI-LOG-007 |
| AUTH-3 | UI-LOG-008, UI-FLOW-002 |
| PROB-1 | UI-COM-005, UI-LIST-001, UI-DETAIL-001 |
| PROB-2 | UI-LIST-004 ~ UI-LIST-010 |
| PROB-3 | UI-RBAC-001 ~ UI-RBAC-005 |
| ADMIN-1 | UI-ADMIN-NEW-001 ~ UI-ADMIN-NEW-008 |
| ADMIN-2 | UI-ADMIN-EDIT-001 ~ UI-ADMIN-EDIT-006, UI-ADMIN-DEL-001 ~ UI-ADMIN-DEL-004 |
| SUB-1 | UI-SUB-001, UI-SUB-002 |
| SUB-2 | UI-SUB-003 ~ UI-SUB-006, UI-FLOW-003 |
| JUDGE-1 | UI-VERDICT-001 |
| JUDGE-2 | UI-VERDICT-002 |
| JUDGE-3 | UI-VERDICT-003 |
| JUDGE-4 | UI-VERDICT-004 |
| JUDGE-5 | UI-VERDICT-005 |
| SUB-3 | UI-HIST-001 ~ UI-HIST-005 |
| CEIL-1 | UI-COM-001 ~ UI-COM-004 |
| MPA-1 | UI-COM-006 ~ UI-COM-008 |

---

## 2. 测试用例索引

| ID | 模块 | 标题 | 优先级 |
|---|---|---|---|
| UI-COM-001 | 公共 | 首页 HTML 可访问(200 + text/html) | P0 |
| UI-COM-002 | 公共 | `/static/*` 静态资源 200 | P0 |
| UI-COM-003 | 公共 | 未知 URL 返回 404 或正确 fallback | P1 |
| UI-COM-004 | 公共 | `viewport` / 标题 / favicon 渲染正确 | P2 |
| UI-COM-005 | 公共 | 未登录也能访问题目列表与详情(PROB-1) | P0 |
| UI-COM-006 | 公共 | 浏览器后退/前进走真实历史栈(MPA-1) | P0 |
| UI-COM-007 | 公共 | 真实 URL 可分享 / 收藏 | P1 |
| UI-COM-008 | 公共 | URL 中无 hash 路由 | P1 |
| UI-COM-009 | 公共 | navbar 高亮当前页 | P1 |
| UI-COM-010 | 公共 | navbar 登录态切换(登录/注销显示) | P1 |
| UI-REG-001 | 注册 | 注册页 200 + 含表单 | P0 |
| UI-REG-002 | 注册 | 合法用户注册成功并跳转到 `/login` | P0 |
| UI-REG-003 | 注册 | 用户名 < 3 字符提示并阻止提交 | P0 |
| UI-REG-004 | 注册 | 用户名 > 20 字符提示并阻止提交 | P0 |
| UI-REG-005 | 注册 | 密码 < 6 字符提示并阻止提交 | P0 |
| UI-REG-006 | 注册 | 重复用户名提示并阻止提交(409 → 友好提示) | P0 |
| UI-REG-007 | 注册 | 两次密码不一致提示 | P1 |
| UI-REG-008 | 注册 | 必填项为空时按钮禁用 / 提示 | P1 |
| UI-REG-009 | 注册 | 注册成功后 toast 反馈 | P2 |
| UI-LOG-001 | 登录 | 登录页 200 + 含表单 | P0 |
| UI-LOG-002 | 登录 | 正确凭据登录后跳转(`?next=` 优先) | P0 |
| UI-LOG-003 | 登录 | 错误密码提示「用户名或密码错误」 | P0 |
| UI-LOG-004 | 登录 | 不存在的用户名提示同样文案(timing-safe) | P1 |
| UI-LOG-005 | 登录 | 空字段提交按钮禁用 / 提示 | P1 |
| UI-LOG-006 | 登录 | 登录后 navbar 显示用户名 | P1 |
| UI-LOG-007 | 登录 | 登录后 Set-Cookie 写入浏览器 | P0 |
| UI-LOG-008 | 登录 | `/api/auth/me` 登录后返回 200 + 用户信息(前端 store 缓存) | P0 |
| UI-LOGOUT-001 | 注销 | navbar 注销按钮可点击 | P1 |
| UI-LOGOUT-002 | 注销 | 注销后跳转 `/` 并清空 cookie | P0 |
| UI-LIST-001 | 列表 | 题目列表页 200,分页 / 难度 / 标签 UI 存在 | P0 |
| UI-LIST-002 | 列表 | 默认显示第一页(page=1, page_size=20) | P1 |
| UI-LIST-003 | 列表 | 列表项不含 description / sample_*(仅 summary) | P1 |
| UI-LIST-004 | 列表 | 按难度筛选 Easy 仅显示 Easy 题 | P0 |
| UI-LIST-005 | 列表 | 按难度筛选 Medium / Hard 各自正确 | P1 |
| UI-LIST-006 | 列表 | 按标签筛选 `array` 仅显示含 array 题 | P0 |
| UI-LIST-007 | 列表 | 难度 + 标签组合筛选正确 | P0 |
| UI-LIST-008 | 列表 | 翻页(next / prev)请求 `?page=N` | P1 |
| UI-LIST-009 | 列表 | 列表为空时友好空态 | P2 |
| UI-LIST-010 | 列表 | 筛选非法值(difficulty=extreme / tag=空)UI 给出提示 | P1 |
| UI-LIST-011 | 列表 | 每行可点击进入详情页(链接真实 `<a href>`) | P1 |
| UI-DETAIL-001 | 详情 | 题目详情页 200,含描述 / 样例 / 限制 | P0 |
| UI-DETAIL-002 | 详情 | 渲染 Markdown 描述(标题 / 列表 / 行内代码) | P1 |
| UI-DETAIL-003 | 详情 | 样例输入 / 样例输出展示 | P1 |
| UI-DETAIL-004 | 详情 | 时间限制 / 内存限制 / 难度 / 标签显示 | P1 |
| UI-DETAIL-005 | 详情 | 不显示测试用例细节(无 SECRET 泄漏) | P0 |
| UI-DETAIL-006 | 详情 | 不存在的 `:id` 显示 404 页 / 友好提示 | P1 |
| UI-DETAIL-007 | 详情 | `:id` 非法(abc / -1 / 0)显示 400 提示 | P1 |
| UI-EDIT-001 | 编辑器 | ace.js 加载并初始化编辑器 | P0 |
| UI-EDIT-002 | 编辑器 | 默认模板代码填充 | P0 |
| UI-EDIT-003 | 编辑器 | C++ 语法高亮(关键字 / 字符串 / 注释颜色不同) | P1 |
| UI-EDIT-004 | 编辑器 | 行号显示 | P2 |
| UI-EDIT-005 | 编辑器 | 括号匹配(输入 `{` 自动补 `}`) | P2 |
| UI-EDIT-006 | 编辑器 | 编辑内容未提交时刷新页面提示 / 保留草稿 | P2 |
| UI-SUB-001 | 提交 | 未登录点击提交按钮 → 跳转 `/login?next=/problems/:id` | P0 |
| UI-SUB-002 | 提交 | 登录后点击提交 → 立即显示 spinner + `submission_id` 拿到 | P0 |
| UI-SUB-003 | 提交 | 提交按钮在请求中禁用防止重复提交 | P1 |
| UI-SUB-004 | 提交 | 提交空代码 → 400 友好提示 | P1 |
| UI-SUB-005 | 提交 | 提交空白代码 → 400 友好提示 | P1 |
| UI-SUB-006 | 提交 | 提交超长代码 → 400 友好提示 | P1 |
| UI-SUB-007 | 提交 | 提交过程中页面 spinner + 「判题中...」文案 | P0 |
| UI-SUB-008 | 提交 | 每 500ms 轮询 `/api/submissions/:id` | P0 |
| UI-SUB-009 | 提交 | 状态为 Pending / Judging 时继续轮询 | P0 |
| UI-SUB-010 | 提交 | 状态为终态时停止轮询并展示结果 | P0 |
| UI-SUB-011 | 提交 | 网络异常时显示重试按钮 | P2 |
| UI-HIST-001 | 记录 | 未登录访问 `/submissions` → 跳转 `/login?next=/submissions` | P0 |
| UI-HIST-002 | 记录 | 登录后 `/submissions` 展示本人提交列表 | P0 |
| UI-HIST-003 | 记录 | 列表默认按时间倒序 | P0 |
| UI-HIST-004 | 记录 | 列表只显示当前用户(不混入他人) | P0 |
| UI-HIST-005 | 记录 | 最多展示 50 条(SPE §6.3 / SUB-3) | P0 |
| UI-HIST-006 | 记录 | `?problem_id=` 过滤生效 | P1 |
| UI-HIST-007 | 记录 | 每行展示 状态 / 题目 / 时间 | P1 |
| UI-HIST-008 | 记录 | 点击某条进入提交详情页 | P1 |
| UI-SUB-DETAIL-001 | 详情 | 未登录访问别人的 `/submissions/:id` → 404(不暴露存在性) | P0 |
| UI-SUB-DETAIL-002 | 详情 | 登录用户访问自己 submission → 200 | P0 |
| UI-SUB-DETAIL-003 | 详情 | admin 可访问任意 submission → 200 | P0 |
| UI-SUB-DETAIL-004 | 详情 | 显示状态徽章 + 题目 + 代码 | P1 |
| UI-SUB-DETAIL-005 | 详情 | 显示代码(只读 / 可复制) | P1 |
| UI-SUB-DETAIL-006 | 详情 | 终态显示 error_msg / time_used / memory_used | P1 |
| UI-SUB-DETAIL-007 | 详情 | Pending 状态下显示「判题中...」+ 自动刷新 | P1 |
| UI-VERDICT-001 | 状态 | AC 绿色「通过」徽章 | P0 |
| UI-VERDICT-002 | 状态 | WA 红色徽章 + 首失败用例 Expected / Got Diff | P0 |
| UI-VERDICT-003 | 状态 | TLE 黄色徽章 + 用时 | P0 |
| UI-VERDICT-004 | 状态 | RE 红色徽章 + 退出码 / 信号名 | P0 |
| UI-VERDICT-005 | 状态 | CE 红色徽章 + 完整 g++ 输出(`<pre>`) | P0 |
| UI-VERDICT-006 | 状态 | Pending / Judging 时显示 spinner + 文案 | P1 |
| UI-VERDICT-007 | 状态 | SE(系统错误)显示「判题服务异常」 | P2 |
| UI-ADMIN-LIST-001 | 后台 | 未登录访问 `/admin/problems` → 跳转 `/login` | P0 |
| UI-ADMIN-LIST-002 | 后台 | role=user 访问 → 显示 403 HTML 页 | P0 |
| UI-ADMIN-LIST-003 | 后台 | role=admin 访问 → 200 + 题目列表 | P0 |
| UI-ADMIN-LIST-004 | 后台 | 列表每行有「编辑 / 删除」按钮 | P1 |
| UI-ADMIN-LIST-005 | 后台 | 删除按钮二次确认 | P1 |
| UI-ADMIN-LIST-006 | 后台 | 「新建题目」按钮可点击跳转 `/admin/problems/new` | P1 |
| UI-ADMIN-LIST-007 | 后台 | 编辑按钮跳转 `/admin/problems/:id/edit` | P1 |
| UI-ADMIN-NEW-001 | 新建 | 表单 200 + 含全部字段 | P0 |
| UI-ADMIN-NEW-002 | 新建 | 合法提交 → 200 + 跳回列表 | P0 |
| UI-ADMIN-NEW-003 | 新建 | 缺 title 阻止提交 | P0 |
| UI-ADMIN-NEW-004 | 新建 | difficulty 不在 {Easy, Medium, Hard} 阻止 | P0 |
| UI-ADMIN-NEW-005 | 新建 | time_limit_ms 越界阻止 | P0 |
| UI-ADMIN-NEW-006 | 新建 | memory_limit_mb 越界阻止 | P0 |
| UI-ADMIN-NEW-007 | 新建 | 描述为空阻止 | P1 |
| UI-ADMIN-NEW-008 | 新建 | 测试用例动态增删(行 +/-) | P0 |
| UI-ADMIN-NEW-009 | 新建 | 测试用例 input / expected_output 缺失阻止 | P1 |
| UI-ADMIN-NEW-010 | 新建 | tags 接受 CSV 字符串与 JSON 数组 | P1 |
| UI-ADMIN-EDIT-001 | 编辑 | `?id=` 加载题目数据回填 | P0 |
| UI-ADMIN-EDIT-002 | 编辑 | 全字段更新保存成功 | P0 |
| UI-ADMIN-EDIT-003 | 编辑 | 不传 test_cases → 保留旧用例 | P1 |
| UI-ADMIN-EDIT-004 | 编辑 | 传空数组 → 删除全部用例 | P1 |
| UI-ADMIN-EDIT-005 | 编辑 | 编辑不存在的 id → 404 友好页 | P1 |
| UI-ADMIN-EDIT-006 | 编辑 | 编辑页有「取消」按钮回退 | P2 |
| UI-ADMIN-DEL-001 | 删除 | admin 删除已存在题目 → 200 + 列表移除 | P0 |
| UI-ADMIN-DEL-002 | 删除 | 二次确认取消则不删 | P1 |
| UI-ADMIN-DEL-003 | 删除 | 删除不存在的 id → 404 提示 | P1 |
| UI-ADMIN-DEL-004 | 删除 | 删除后级联清空该题 submissions(DB 一致性) | P0 |
| UI-RBAC-001 | 鉴权 | anonymous 直接 GET `/admin/*` 任一页面 → 跳转 `/login?next=...` | P0 |
| UI-RBAC-002 | 鉴权 | role=user GET `/admin/*` → 显示 403 HTML | P0 |
| UI-RBAC-003 | 鉴权 | role=admin GET `/admin/*` → 200 | P0 |
| UI-RBAC-004 | 鉴权 | anonymous POST `/api/submissions` → 401 + 跳转 | P0 |
| UI-RBAC-005 | 鉴权 | cross-user GET `/submissions/:id` → 404(不暴露存在性) | P0 |
| UI-FLOW-001 | 流程 | 注册 → 登录 → 浏览 → 提交 → 看到 AC(AUTH-1+SUB-1+JUDGE-1) | P0 |
| UI-FLOW-002 | 流程 | 登录失败 → 重新输入 → 成功(AUTH-2) | P0 |
| UI-FLOW-003 | 流程 | 提交后 30 秒内拿到终态(轮询生效) | P0 |
| UI-FLOW-004 | 流程 | 注销后再次访问 `/submissions` → 跳转登录 | P1 |
| UI-FLOW-005 | 流程 | admin 建题 → 切换普通账号 → 提交 → AC(ADMIN-1+SUB-1) | P0 |
| UI-FLOW-006 | 流程 | 普通账号访问 admin URL → 看到 403 页 | P0 |
| UI-NF-001 | 非功能 | 首页 HTML 大小 < 100 KB | P2 |
| UI-NF-002 | 非功能 | `/static/js/ace/...` 体积约 500 KB(MVP 接受) | P2 |
| UI-NF-003 | 非功能 | 浏览器 Console 无 JS 错误 | P1 |
| UI-NF-004 | 非功能 | 移动端 viewport 列表 / 详情可用 | P2 |
| UI-NF-005 | 非功能 | 重复打开同一题页编辑器保留上次内容(localStorage 可选) | P2 |
| UI-NF-006 | 非功能 | 长错误信息(CE 输出 > 50 行)`<pre>` 横向滚动 | P2 |
| UI-NF-007 | 非功能 | F5 刷新不会丢失表单 / 列表状态(MPA 真实导航) | P2 |

最终生成的web_testcase.md见链接:https://gitee.com/bacht/oj_-platform-based_on-vibecoding/blob/master/API.md

4. 构造测试数据(重置数据库中的数据 的工具,不对外暴露)

虽然测试用例文档已经有了,但是还不能直接开始测试。

测试能通过,功能能正确,取决于两方面:

  • 代码逻辑正确
  • 数据库中的数据正确

要想验证代码逻辑,需要先保证数据库中,有正确的测试数据。

由于前面已经进行了多轮 单元测试 和 接口自动化测试,数据库中已经有很多 "测试数据"。这些数据可能就对接下来的测试造成负面干扰。

因此需要先确保数据库是 "干净" 的,把冗余数据删除掉,只保留需要的数据。这个过程不需要手动完成,可以让 AI 写⼀个工具,完成 "数据库重置工作"

  • 提示词
bash 复制代码
@SPEC.md @web_testcase.md 根据 web_testcase.md⽂档, 编写⼀个程序(C++), 实现对数据库进行重置的效果, 
删除数据库中 冗余的内容, 构造 web接⼝自动化需要的必要数据.程序放到 oj-server/src/tools 目录下

新增文件 oj-server/src/tools/seed_test_data.cpp(+CMakeLists.txt 注册为 seed_test_data 可执行)。

做的事(单事务包整个 reset + seed):

阶段 内容
reset DELETE 5 张表(显式顺序 submissions→test_cases→sessions→problems→users,避免 CASCADE 静默吞表)+ ALTER TABLE AUTO_INCREMENT=1
seed users admin/admin123(管理员)、alice/alice123(普通用户)、bob/bob123(普通用户),每个用 oj::GenerateSaltHex + oj::HashPassword,每条 INSERT 后同连接 VerifyPassword round-trip 自检
seed problems 6 named(TWO-SUM/GRAPH-BFS/DP-KNAPSACK/SECRET-LEAK/ECHO-PRINT/SYNTAX-ERR)+ 17 filler,覆盖 Easy/Medium/Hard 与 array/graph/dp/math/io/beginner 等 tag
seed submissions alice 61 条(5 verdict demo + 1 SECRET-LEAK WA + 55 AC filler)+ bob 2 条 AC

执行这个工具的命令:./build/seed_test_data --config oj-server/config.json
执行过程中在终端会要求输入 YES 确认(--yes 跳过)
重置数据库中的数据 其实还是挺危险的!所以需要增加 确认操作(在终端输入 YES 确认)

echo YES | ./build/seed_test_data --config oj-server/config.json 或 --yes 可以跳过在终端确认的交互;--help 看用法;默认工作目录在仓库根(与 oj_server 一致)。退出码 0/1/2/3 与 reset_admin 对齐。

5. linux端启动服务器,windows端进行 Web 自动化测试

5.1 准备阶段(重置数据库中数据;启动服务器)

(1) 记得在Linux端,先输入命令 ./build/seed_test_data --config oj-server/config.json,使用重置数据库中数据 的工具,清理数据库,构造测试数据

bash 复制代码
ubuntu@VM-0-4-ubuntu:~/project/oj_-platform-based_on-vibecoding$ ./build/seed_test_data --config oj-server/config.json
[seed_test_data] config         : oj-server/config.json
[seed_test_data] db             : ubuntu@127.0.0.1:3306/oj
[seed_test_data] current state  : users=3 problems=23 test_cases=27 submissions=63 sessions=5

!!! WARNING !!!
About to RESET the OJ database:
  - DROP every row in {submissions, test_cases, sessions,
    problems, users}, in that order.
  - Reset AUTO_INCREMENT to 1 on all 5 tables.
  - Re-seed 3 users (admin / alice / bob) + 23 problems +
    ~63 submissions needed by web_testcase.md.

All previously created accounts, problems and submissions will
be PERMANENTLY destroyed. Sessions are invalidated. New random
salts are generated for each seeded user.

Type YES (case-sensitive) to continue, anything else to abort:
> YES
[seed_test_data] [reset] DELETE submissions...
[seed_test_data] [reset] DELETE test_cases...
[seed_test_data] [reset] DELETE sessions...
[seed_test_data] [reset] DELETE problems...
[seed_test_data] [reset] DELETE users...
[seed_test_data] [reset] ALTER TABLE ... AUTO_INCREMENT = 1
[seed_test_data] [seed users] admin    id=1 role=admin VerifyPassword(OK)
[seed_test_data] [seed users] alice    id=2 role=user VerifyPassword(OK)
[seed_test_data] [seed users] bob      id=3 role=user VerifyPassword(OK)
[seed_test_data] [seed problems] TWO-SUM        id=1 diff=Easy   tags=array,hash-table cases=2
[seed_test_data] [seed problems] GRAPH-BFS      id=2 diff=Medium tags=graph,bfs cases=2
[seed_test_data] [seed problems] DP-KNAPSACK    id=3 diff=Hard   tags=dp,graph cases=2
[seed_test_data] [seed problems] SECRET-LEAK    id=4 diff=Easy   tags=math cases=2
[seed_test_data] [seed problems] ECHO-PRINT     id=5 diff=Easy   tags=io,beginner cases=1
[seed_test_data] [seed problems] SYNTAX-ERR     id=6 diff=Easy   tags=io,beginner cases=1
[seed_test_data] [seed problems] FILLER-001     id=7 diff=Easy   tags=array,beginner cases=1
[seed_test_data] [seed problems] FILLER-002     id=8 diff=Easy   tags=string,beginner cases=1
[seed_test_data] [seed problems] FILLER-003     id=9 diff=Easy   tags=math,beginner cases=1
[seed_test_data] [seed problems] FILLER-004     id=10 diff=Medium tags=array,two-pointers cases=1
[seed_test_data] [seed problems] FILLER-005     id=11 diff=Medium tags=string,dp cases=1
[seed_test_data] [seed problems] FILLER-006     id=12 diff=Medium tags=math,greedy cases=1
[seed_test_data] [seed problems] FILLER-007     id=13 diff=Medium tags=graph,dfs cases=1
[seed_test_data] [seed problems] FILLER-008     id=14 diff=Hard   tags=graph,shortest-path cases=1
[seed_test_data] [seed problems] FILLER-009     id=15 diff=Hard   tags=dp,segment-tree cases=1
[seed_test_data] [seed problems] FILLER-010     id=16 diff=Easy   tags=array,hash-table cases=1
[seed_test_data] [seed problems] FILLER-011     id=17 diff=Easy   tags=math,simulation cases=1
[seed_test_data] [seed problems] FILLER-012     id=18 diff=Medium tags=graph,topological-sort cases=1
[seed_test_data] [seed problems] FILLER-013     id=19 diff=Medium tags=string,trie cases=1
[seed_test_data] [seed problems] FILLER-014     id=20 diff=Hard   tags=dp,bitmask cases=1
[seed_test_data] [seed problems] FILLER-015     id=21 diff=Hard   tags=graph,flow cases=1
[seed_test_data] [seed problems] FILLER-016     id=22 diff=Easy   tags=io,beginner cases=1
[seed_test_data] [seed problems] FILLER-017     id=23 diff=Medium tags=array,binary-search cases=1
[seed_test_data] [seed submissions] total=63 (alice 5 verdict demo + 1 SECRET-LEAK WA + 55 AC filler + bob 2 AC)

[seed_test_data] COMMIT ok
[seed_test_data] ==== final table counts ====
  users       : 3  (admin, alice, bob)
  problems    : 23  (TWO-SUM=1, GRAPH-BFS=2, DP-KNAPSACK=3, SECRET-LEAK=4, ECHO-PRINT=5, SYNTAX-ERR=6, FILLER-001..017=7..23)
  test_cases  : 27
  submissions : 63  (alice 61, bob 2)
  sessions    : 0  (startup session from oj_server goes here)
{"ok":true,"action":"seed_test_data","users":3,"problems":23,"test_cases":27,"submissions":63,"sessions":0,"known_ids":{"admin":1,"alice":2,"bob":3,"two_sum":1,"graph_bfs":2,"dp_knapsack":3,"secret_leak":4,"echo_print":5,"syntax_err":6},"self_check":"verify_password_ok","self_check_failures":[]}

然后 在linux端启动服务器:


(2)在 windows端创建目录,把刚才的 web_testcase.md文件拷贝进来.

在当前目录中, 打开终端

打开 windows端的opencode

如果在PowerShell启动不了opencode,可以试试如下操作:

5.2 公共布局与导航测试

记得在Linux端,先输入命令 ./build/seed_test_data --config oj-server/config.json,
使用重置数据库中数据 的工具,清理数据库,构造测试数据

(1)回到 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 完成 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请先只执⾏ 3. 公共布局与导航测试
  • 第 3 节「公共布局与导航测试」执行完毕
ID 标题 结果
UI-COM-001 首页 HTML 可访问 ✅ PASS
UI-COM-002 /static/* 静态资源 200 ✅ PASS (附缺陷)
UI-COM-003 未知 URL 返回 404 ⚠️ PARTIAL (附缺陷)
UI-COM-004 viewport / 标题 / favicon ✅ PASS
UI-COM-005 未登录也能访问列表与详情 ✅ PASS (附轻微偏差)
UI-COM-006 后退/前进真实历史栈 ✅ PASS
UI-COM-007 真实 URL 可分享 ✅ PASS
UI-COM-008 URL 中无 hash 路由 ✅ PASS
UI-COM-009 navbar 高亮当前页 ✅ PASS (注)
UI-COM-010 navbar 登录态切换 ✅ PASS
  • 发现的缺陷汇总

    1. /static/js/app.js 返回 404 --- 首页请求了一个不存在的 JS 文件(无引用源,疑似残留引用)
    2. /static/vendor/ace/mode-c_cpp.js 与 theme-tomorrow_night.js 返回 404 --- 详情页 ace 编辑器无法加载 C++ 语法高亮
    3. /api/auth/this-too 等未知 API 路径返回空 body 且无 Content-Type --- 应返回 JSON 404
    4. 未登录详情页的「提交」按钮为 disabled --- spec 期望可点击后跳转登录(功能上等价:页面提供「登录」链接)
  • 备注

    • UI-COM-009:实现将 .active 加在 < a > 上而非 < li > 上,视觉上等价
    • UI-COM-010:注销按钮是 icon-only(带 title="退出登录"),无文字「注销」

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(只执⾏ 3. 公共布局与导航测试),发现了以下缺陷:
	1. /static/js/app.js 返回 404 --- 首页请求了一个不存在的 JS 文件(无引用源,疑似残留引用)
	2. /static/vendor/ace/mode-c_cpp.js 与 theme-tomorrow_night.js 返回 404 --- 详情页 ace 编辑器无法加载 C++ 语法高亮
	3. /api/auth/this-too 等未知 API 路径返回空 body 且无 Content-Type --- 应返回 JSON 404
	4. 未登录详情页的「提交」按钮为 disabled --- spec 期望可点击后跳转登录(功能上等价:页面提供「登录」链接)
请进行修复

四项缺陷全部修复,149 个测试全部通过(20 C++ + 55 DOM + 74 Python)。

  • 改动汇总
缺陷 修复点 关键文件
1./static/js/app.js 404 新增显式 app.js 入口(声明 OJ.app namespace) oj-server/web/static/js/app.js
2.ace mode/theme 404 从 ace-builds@1.32.7 (mailto:ace-builds@1.32.7) src-noconflict 拉入两份模块 oj-server/web/static/vendor/ace/mode-c_cpp.js oj-server/web/static/vendor/ace/theme-tomorrow_night.js
3./api/auth/this-too 空 body 新增 NotFoundJson() + set_error_handler;/:tail(.*) 兜底对多段路径无法生效(PathParamsMatcher 只吃单段),改用 error-handler 统一兜底 JSON oj-server/src/server/html_serve.h oj-server/src/server/html_serve.cpp
4.提交按钮 disabled 移除 anon 状态下的 disabled=true,同步更新 hint 文案为「点击后跳转到登录页」 oj-server/web/problem.html oj-server/web/static/js/pages/problem_detail.js:338-354
  • 验证

HTTP/1.1 404 Not Found /static/js/app.js 200

Content-Type: application/json /static/vendor/ace/mode-c_cpp.js 200

{"error":"not_found", /static/vendor/ace/theme-tomorrow_night.js 200

"message":"unknown api path: /this-does-not-exist 404 HTML(保持原样)

/api/auth/this-too"} /api/problems 200(兜底没吞)

/api/health 200

  • 新增测试
    • tests/python/test_07_layout.py --- 8 个用例(静态资源 200 + /api/ JSON 404 + HTML 兜底回归)
    • tests/dom/problem-anon.test.js --- 10 个用例(anon 按钮可点 + hint 文案 + toast 触发 + 登录回归)

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证
  • 全部缺陷已修复并验证
缺陷 修复前 修复后
1./static/js/app.js 404 404 Not Found ✅ 200 OK, text/javascript, 1151 B
2./static/vendor/ace/mode-c_cpp.js 404 404 Not Found ✅ 200 OK, text/javascript, 19778 B
3./static/vendor/ace/theme-tomorrow_night.js 404 404 Not Found ✅ 200 OK, text/javascript, 3961 B
4./api/auth/this-too 空 body、无 Content-Type 空 body ✅ JSON 404 {"error":"not_found","message":"unknown api path: /api/auth/this-too"}, application/json; charset=utf-8
5.未登录「提交代码」按钮 disabled disabled ✅ 可点击, 点击后跳转 /login?next=%2Fproblems%2F1

控制台也清洁 --- 仅剩预期的 401(/api/auth/me 未登录) 与 ace.js 的拼写警告(非缺陷)。

5.3 首页与未登录浏览 和 注册 / 登录 / 注销 UI 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "4. 首页与未登录浏览" 和 "5. 注册 / 登录 / 注销 UI"

第 4 + 5 节测试总结:全部通过 (19 PASS / 2 PARTIAL / 1 FAIL)

ID 用例 结果 关键证据
UI-HOME-001 首页入口齐全 ✅ PASS 5 个 (开始刷题/浏览题库→/problems,立即注册/创建账号→/register) + navbar 登录/注册
UI-REG-001 注册页 200 + 表单 ✅ PASS 用户名/密码/确认密码 + 注册并登录按钮
UI-REG-002 合法注册成功 ⚠️ PARTIAL DB 创建成功 (id 851 charlie26),但实际自动登录跳 /problems(非 spec 的跳 /login)
UI-REG-003 用户名 < 3 字符 ✅ PASS alert「用户名长度需在 3-20 字符之间」
UI-REG-004 用户名 > 20 字符 ✅ PASS maxLength=20 阻断输入;绕过后 alert「用户名长度需在 3-20 字符之间」
UI-REG-005 密码 < 6 字符 ✅ PASS alert「密码至少 6 字符」
UI-REG-006 重复用户名 ✅ PASS alert「用户名已被占用,请换一个」
UI-REG-007 两次密码不一致 ✅ PASS alert「两次输入的密码不一致」
UI-REG-008 必填项空 ✅ PASS alert「请输入用户名」
UI-REG-009 注册成功 toast ❌ FAIL 无 toast,无「注册成功」文案,仅 navbar 切换 + 跳 /problems
UI-LOG-001 登录页 200 + 表单 ✅ PASS 用户名/密码 + 登录按钮
UI-LOG-002 登录跳 next ✅ PASS ?next=/problems/3 → 登录后跳 /problems/3
UI-LOG-002b 登录默认跳转 ⚠️ PARTIAL 默认跳 /problems(非 spec 的 / 或 /submissions)
UI-LOG-003 错误密码提示 ✅ PASS alert「用户名或密码错误」
UI-LOG-004 不存在用户名 ✅ PASS 同 alert「用户名或密码错误」(无枚举漏洞)
UI-LOG-005 空字段 ✅ PASS alert「请输入用户名」
UI-LOG-006 navbar 显示用户名 ✅ PASS 「alice / user」可见,登录/注册隐藏,管理后台隐藏
UI-LOG-007 Set-Cookie ✅ PASS OJ_SESSION 64-char hex, HttpOnly, Path=/, SameSite=Lax
UI-LOG-008 /api/auth/me ✅ PASS 200 {user:{id, username:"alice", role:"user"}}
UI-LOGOUT-001 注销按钮 ✅ PASS data-logout-btn title="退出登录"
UI-LOGOUT-002 注销清 cookie ✅ PASS 跳 /,cookie \[\],/api/auth/me 401,/submissions 跳 /login?next=/submissions

新发现缺陷

  1. DEFECT-R1: 注册成功后无 toast 反馈 (UI-REG-009 失败)
  2. DEFECT-R2: 注册/登录默认跳转 /problems 而非 /login 或 /(与 spec §3 / 5 节期望不符)

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(执⾏ "4. 首页与未登录浏览" 和 "5. 注册 / 登录 / 注销 UI"),发现了以下缺陷:
	1. DEFECT-R1: 注册成功后无 toast 反馈 (UI-REG-009 失败)
	2. DEFECT-R2: 注册/登录默认跳转 /problems 而非 /login 或 /(与 spec §3 / 5 节期望不符)
请进行修复

两项缺陷均已修复。

缺陷 修复点 关键文件
R1 注册成功后无 toast 反馈(UI-REG-009) toast 文案从 '账号创建成功,欢迎加入!' 改为 '注册成功,请登录'(与 UI-REG-002 期望文案一致),保留 success / 1800ms oj-server/web/static/js/pages/register.js:118
R2 注册/登录默认跳 /problems register 拆出 POST_REGISTER_TARGET = '/login'(覆盖 SPEC §3 注册流程)+ ALREADY_LOGGED_IN_TARGET = '/';login 的 DEFAULT_NEXT 改为 '/' register.js:29-31、login.js:26
同步更新品牌区宣传文案 「跳转回题库」→「跳转回登录页」(避免与新行为冲突) oj-server/web/register.html:24

测试

  • 新增 tests/dom/auth-flow.test.js(11 用例):覆盖 register toast 文案 / 跳转目标、login 默认跳转 /、?next= 同源透传、open-redirect 防护、已登录访 /register 与 /login 的兜底跳转。
  • 全部回归:20 C++ + 74 Python + 66 DOM(45 cancel-button + 10 problem-anon + 11 auth-flow)= 160 用例 100% pass。

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证

✅ DEFECT-R2 修复成功 --- 直接访问 /login 登录后跳转 / (首页),Title 为 OJ Platform · 在线判题系统。

✅ 全部缺陷已修复并验证

缺陷 修复前 修复后
DEFECT-R1: 注册成功 toast + 跳转 无 toast,跳 /problems ✅ .toast.toast--success 显示「注册成功,请登录」(role=status)+ 跳 /(已自动登录)
DEFECT-R2: 登录默认跳转 跳 /problems ✅ 跳 /(首页),Title OJ Platform · 在线判题系统

5.4 题目列表页 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "6. 题目列表页"
  • 第 6 节测试结果
用例 结果 备注
UI-LIST-001 ✅ PASS 200,标签/难度/每页/翻页 控件齐全
UI-LIST-002 ✅ PASS 20 条/页、共 23 题、当前页 1 高亮
UI-LIST-003 ✅ PASS 列表无 description/sample_input/sample_output
UI-LIST-004 ✅ PASS Easy: 10 条全部 Easy
UI-LIST-005 ✅ PASS Medium: 8 条全部 Medium;Hard: 5 条全部 Hard
UI-LIST-006 ✅ PASS tag=array: 5 条全含 array
UI-LIST-007 ✅ PASS Hard+graph: 3 条全 Hard+含 graph
UI-LIST-008 ✅ PASS 下一页→?page=2(3,2,1);上一页→/(page 1)
UI-LIST-009 ✅ PASS 空态「没有符合条件的题目」+「清除筛选」按钮
UI-LIST-010 ⚠️ PARTIAL URL 不变/不崩溃 ✅,无友好错误提示 ❌
UI-LIST-011 ✅ PASS ,点击进入详情页

此处只有一处无关紧要的错误,就不去修复了

5.5 题目详情页(含编辑器与提交) 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "7. 题目详情页(含编辑器与提交)"
  • 第 7 节测试结果
用例 结果 备注
UI-DETAIL-001 ✅ PASS /problems/1 200,标题/难度/标签/描述/样例/限制/编辑器/提交按钮齐全
UI-DETAIL-002 ⚠️ PARTIAL 描述只渲染 \code`/ /分段;#/##/bold/- 列表显示为原文。XSS 防护 ✅
UI-DETAIL-003 PASS
UI-DETAIL-005 ✅ PASS /api/problems/1 响应仅含 problem+sample_io,无 test_cases;页面 HTML 全文无 SECRET/test_case
UI-DETAIL-006 ✅ PASS /problems/99999999 → 「找不到该题目」+「返回题库」
UI-DETAIL-007 ✅ PASS /abc、/0、/-1、/9999...9 → 「题目 id 不合法」,无崩溃
UI-EDIT-001 ✅ PASS ace.js + mode-c_cpp + theme-tomorrow_night 均 200;.ace_editor 节点存在;输入有响应
UI-EDIT-002 ✅ PASS 默认含 #include <bits/stdc++.h> + using namespace std; + int main() {... return 0;}
UI-EDIT-003 ✅ PASS int 紫色、main 浅灰、return 紫色、0 橙色,分类明确
UI-EDIT-004 ✅ PASS 4 行输入 → gutter 显示 4 个行号 cell
UI-EDIT-005 ✅ PASS { x } 输入后,{ 与 } 均获得 ace_bracket ace_start ace_br15 高亮
UI-EDIT-006 ❌ FAIL localStorage
  • 本节新缺陷
    1. DEFECT-L2: 题目描述 Markdown 不完整 --- 仅支持 \code`/
      /分段,# ## bold -` 列表等标准 Markdown 语法未实现(源码注释 "We keep this intentionally minimal")
    2. DEFECT-L4: 编辑器草稿不持久化 --- problem_detail.js 在 mountEditor() 中给 state.editor.el(原始 textarea)挂 input 监听器,但 editor.js 在 ace 加载后执行 host.innerHTML = '' 移除了 textarea;源码注释 "also listen to ace's change event after it loads" 之后无实现;同时无 beforeunload 提示

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(执⾏ "7. 题目详情页(含编辑器与提交)"),发现了以下缺陷:
	1. DEFECT-L2: 题目描述 Markdown 不完整 --- 仅支持 \code\`/<br>/分段,# ## bold -` 列表等标准 Markdown 语法未实现(源码注释 "We keep this intentionally minimal")
	2. DEFECT-L4: 编辑器草稿不持久化 --- problem_detail.js 在 mountEditor() 中给 state.editor.el(原始 textarea)挂 input 监听器,但 editor.js 
在 ace 加载后执行 host.innerHTML = '' 移除了 textarea;源码注释 "also listen to ace's change event after it loads" 之后无实现;同时无 beforeunload 提示
请进行修复

修复完成。

(1) DEFECT-L2 --- Markdown 渲染

  • oj-server/web/static/js/pages/problem_detail.js:146-262:重写 renderDescription(行内抽出为 renderInline),管线 4 段:
    1. fenced code ... 占位符抽离,body 稍后才 escape
    2. 按行 walk,分块(heading / list / paragraph / fence)
    3. 段内/项内由 renderInline 处理:先 escapeHtml 阻断 XSS,再依次套上 codebolditalic(顺序避免 ** 与 * 互吃)
    4. 单换行 →
      ,空行分段
  • oj-server/web/static/css/pages.css:968-1000:补 .problem-statement__heading-inline--h1...h6 / __list / / 样式。
  • oj-server/web/*.html:7 个 HTML 的 cache buster ?v=2 → ?v=3。

(2) DEFECT-L4 --- 编辑器草稿持久化 + beforeunload

  • oj-server/web/static/js/pages/problem_detail.js:255-309:把 state.editor.el.addEventListener('input', ...) 这条「ace 一升级就死」的监听换成 OJ.editor.create({ onChange: schedulePersist }) ------ editor.js:115-151 在 ace 路径(session.on('change', ...))和 textarea fallback 路径(input)都会回调 opts.onChange,debounce 400ms 后写入 localStorage"oj:code:"
  • oj-server/web/static/js/pages/problem_detail.js:312-325,734:新增 onBeforeUnload,window.addEventListener('beforeunload', ...):当 state.editor.getValue() !== defaultStarter() 时 preventDefault() + e.returnValue = '',触发浏览器原生「确认重新提交」提示(UI-EDIT-006)。

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证
  • 第 7 节回归结果
缺陷 验证结果 修复说明
DEFECT-L2 (Markdown 渲染不全) ✅ 已修复 # ## → < h1 > < h2 >、bold → < strong >、- 列表 → < ul >< li > 全部正确渲染。新增 renderInline helper,顺序:escape → code → bolditalic
DEFECT-L4 (草稿不持久化) ✅ 已修复 (a) mountEditor() 改用 onChange: schedulePersist 回调(由 OJ.editor.create() 内部同时绑定 ace session.on('change') 与 textarea input);(b) 新增 onBeforeUnload 处理器 + window.addEventListener('beforeunload'),编辑器内容 ≠ 默认模板时弹出原生提示

5.6 提交记录与提交详情页 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "8. 提交记录与提交详情页"

第 8 节「提交记录与提交详情页」测试结果

  • UI-SUB-001~011: 提交流程
用例 结果 备注
UI-SUB-001 未登录 → /login?next= ✅ PASS 点击提交后跳 /login?next=%2Fproblems%2F1,登录后回到 /problems/1
UI-SUB-002 spinner + submission_id ✅ PASS 提交后 label "提交中..." → "再次提交",verdict 显示 #918(submission_id)
UI-SUB-003 提交中按钮禁用 ✅ PASS 500ms 内 button.disabled=true, label="提交中..."
UI-SUB-004 空代码 → 友好提示 ✅ PASS toast "代码不能为空"
UI-SUB-005 空白代码 → 友好提示 ✅ PASS toast "代码不能为空"
UI-SUB-006 超长代码 → 友好提示 ⚠️ PARTIAL 4 MiB+1 字节代码:400 toast 显示 code exceeds 4194304 bytes (got 4194305)(英文,非中文「代码超过最大长度」)
UI-SUB-007 spinner + 「判题中...」文案 ✅ PASS verdict label="等待判题",msg="已等待...秒...",icon
UI-SUB-008 每 500ms 轮询 ⚠️ PARTIAL 实际间隔 1025ms(≈1s),规格写 ~500ms。轮询模式正确(Pending→Judging→终态→停止)
UI-SUB-009 Pending/Judging 继续轮询 ✅ PASS 源码 pollOnce() 在非终态时 scheduleNextPoll,实测符合
UI-SUB-010 终态停止轮询 ✅ PASS 终态后 spinner 消失,徽章 "通过"(AC),SVG checkmark icon
UI-SUB-011 网络异常显示重试按钮 ❌ FAIL 阻断轮询后没有"重试"按钮,只有"停止等待"按钮。源码 problem_detail.js 无 retry 按钮实现
  • UI-HIST-001~008: 提交历史列表
用例 结果 备注
UI-HIST-001 未登录 → 登录跳转 ✅ PASS /submissions → /login?next=%2Fsubmissions
UI-HIST-002 展示本人提交列表 ✅ PASS alice 的 65 条记录全部展示
UI-HIST-003 默认时间倒序 ✅ PASS #919(21:45:48) → #918 → ... → #58
UI-HIST-004 只显示当前用户 ✅ PASS API /api/submissions?limit=50 返回 50 条,userIds=2 全部为 alice
UI-HIST-005 最多展示 50 条 ✅ PASS page-size 下拉含 {10,20,50},选 50 后展示 50 条,无「更多」按钮
UI-HIST-006 ?problem_id= 过滤 ✅ PASS alice 在 problem 2 提交 2 次 → /submissions?problem_id=2 展示 2 条(均为 #2)
UI-HIST-007 每行展示 状态/题目/时间 ⚠️ PARTIAL 每行含 ID/状态徽章("通过")/题目(#2)/语言(cpp)/时间,时间显示绝对时间戳 2026-08-01 21:49:23,非相对时间「2 分钟前」
UI-HIST-008 点击行进入详情 ✅ PASS 点击行 → /submissions/921 真实 URL
  • UI-SUB-DETAIL-001~007: 提交详情页
用例 结果 备注
UI-SUB-DETAIL-001 bob 看 alice 的 → 404 ✅ PASS API 404 + 页面 data-submission-notfound 显示,不暴露存在性
UI-SUB-DETAIL-002 alice 看自己的 → 200 ✅ PASS /submissions/921 完整内容
UI-SUB-DETAIL-003 admin 看任意 → 200 ✅ PASS admin 可读 alice 的 #921,main 可见
UI-SUB-DETAIL-004 状态徽章 + 题目 + 代码 ⚠️ PARTIAL 顶部 AC 徽章 + 中间题目标题("查看题目" 链接至 /problems/2) + 代码块 保留缩进 --- 但无语法高亮,纯 转义文本
UI-SUB-DETAIL-005 复制按钮 + 只读 ✅ PASS 复制按钮 label "复制代码" → "已复制",剪贴板写入代码;无编辑器(只读)
UI-SUB-DETAIL-006 用时/内存 + error_msg ✅ PASS AC 提交:用时 "1 ms",内存 "17 MB",finished_at 时间戳
UI-SUB-DETAIL-007 Pending spinner + 自动刷新 ✅ PASS 源码 VERDICT_ICON.Pending='',VERDICT_TEXT.Pending='等待判题',defaultVerdictMsg('Pending')='提交已受理,等待判题...',每 1s 轮询,终态后停止
  • 新增缺陷
缺陷 ID 描述 关联用例
DEFECT-L5 提交历史时间显示绝对时间戳(2026-08-01 21:49:23),非规格要求的相对时间(2 分钟前)。源码 submissions.js:formatTime() 直接 String(s) 透传 ISO 字符串,未实现相对时间计算。 UI-HIST-007
DEFECT-L6 提交详情页代码块无语法高亮,纯 < code > 转义文本。规格要求"含语法高亮"。源码 submission_detail.js:els.code.innerHTML = '< code >' + escapeHtml(code) + '< /code >' 未集成 hljs / Prism / ace render。 UI-SUB-DETAIL-004
DEFECT-L7 缺失"重试"按钮:网络异常时(轮询失败)规格要求显示「重试」按钮,源码 problem_detail.js 仅提供"停止等待"按钮(els.verdictCancel),无 retry UI。 UI-SUB-011
DEFECT-L8 (小) 超长代码错误提示为英文(code exceeds 4194304 bytes (got 4194305)),规格要求中文「代码超过最大长度」。backend 直接返回 err.message,未做 i18n 翻译。 UI-SUB-006
DEFECT-L9 (小) 轮询间隔 1025ms(≈1s)而非规格的 ~500ms。源码 problem_detail.js:POLL_INITIAL_MS=1000 和 submission_detail.js:POLL_INITIAL_MS=1000。轮询行为正确,只是频率慢一倍。 UI-SUB-008

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(执⾏ "8. 提交记录与提交详情页"),发现了以下缺陷:
	1. DEFECT-L5: 提交历史时间显示绝对时间戳(2026-08-01 21:49:23),非规格要求的相对时间(2 分钟前)。源码 submissions.js:formatTime() 直接 String(s) 透传 ISO 字符串,未实现相对时间计算。关联用例:UI-HIST-007
	2. DEFECT-L6: 提交详情页代码块无语法高亮,纯 <code> 转义文本。规格要求"含语法高亮"。源码 submission_detail.js:els.code.innerHTML = '<code>' + escapeHtml(code) + '</code>' 未集成 hljs / Prism / ace render。关联用例:UI-SUB-DETAIL-004
	3. DEFECT-L7: 缺失"重试"按钮:网络异常时(轮询失败)规格要求显示「重试」按钮,源码 problem_detail.js 仅提供"停止等待"按钮(els.verdictCancel),无 retry UI。关联用例:UI-SUB-011
	4. DEFECT-L8 (小): 超长代码错误提示为英文(code exceeds 4194304 bytes (got 4194305)),规格要求中文「代码超过最大长度」。backend 直接返回 err.message,未做 i18n 翻译。关联用例:UI-SUB-006
	5. DEFECT-L9 (小): 轮询间隔 1025ms(≈1s)而非规格的 ~500ms。源码 problem_detail.js:POLL_INITIAL_MS=1000 和 submission_detail.js:POLL_INITIAL_MS=1000。轮询行为正确,只是频率慢一倍。关联用例:UI-SUB-008
请进行修复

5 个缺陷全部修复。

(1)DEFECT-L5 --- 提交历史时间相对化(UI-HIST-007)

  • 新增 oj-server/web/static/js/utils.js:OJ.utils.formatRelativeTime(s, now?) --- 分桶「刚刚 / N 秒前 / N 分钟前 / N 小时前 / N 天前」,≥ 7 天回退绝对时间 YYYY-MM-DD HH:MM,null/空/无效 → ---;支持 ISO 字符串 + Unix 秒/毫秒数 + MySQL YYYY-MM-DD HH:MM:SS 三种格式
  • submissions.js:183-188 formatTime 委托给 utils.js
  • problem_detail.js:813-826 recent list 时间列也改用 formatRelativeTime
  • submissions.html / problem.html / submission.html 加载 utils.js(?v=4)

(2)DEFECT-L6 --- 提交详情页语法高亮(UI-SUB-DETAIL-004)

  • 新增 vendor: oj-server/web/static/vendor/hljs/{highlight.min.js, cpp.min.js, atom-one-dark.min.css}(125 KB core + 6 KB cpp + 856 B css)
  • 新增 oj-server/web/static/js/hljs.js --- lazy-load wrapper:同步路径直接返回 hljs HTML;异步路径先渲染 escaped fallback,load 完成后升级,且用 pendingHighlightCode 哨兵防止旧升级覆盖新渲染
  • submission_detail.js:259-285 renderSubmission 用 OJ.hljs.highlight(code, language),注释保留 hljs 自动 escape 的安全语义
  • submission.html 加载 hljs.js + atom-one-dark CSS,pages.css 新增 .submission-detail__code pre.hljs 重置背景

(3)DEFECT-L7 --- 网络异常显示重试按钮(UI-SUB-011)

  • problem.html + submission.html verdict 区新增 重试(在 cancel 按钮左侧)
  • pages.css 新增 .verdict__retry 样式(accent 配色,与 cancel 的 danger 区分)
  • problem_detail.js + submission_detail.js 同步改动:
  • state 加 pollErrorFlag
  • pollOnce 成功 → pollErrorFlag = false + showVerdict 隐藏重试
  • 首次失败 → pollErrorFlag = true + showVerdict 暴露重试(背景 ticker 同时继续)
  • onVerdictRetryClick() → cancelPoll + 重置 attempts + 立即 scheduleNextPoll(POLL_INITIAL_MS)(500ms 快速 backoff)
  • cancelWaiting / submit / run 清 pollErrorFlag
  • 终态 / 已取消 → retry 永远隐藏

(4)DEFECT-L8 --- 超长代码提示中文 i18n(UI-SUB-006)

  • 后端:新增 CodeTooLongErrorS 异常类型(submission_service.h:84),handler 映射成 400 { error: "code_too_long", message: ... },C++ 测试 RejectsOversizedCode 改验此类型
  • 前端:problem_detail.js:651-664 submit 错误分支优先匹配 err.code === 'code_too_long' → toast「代码超过最大长度」;其它错误走原 fallback
  • 现场验证:curl POST /api/submissions with 4 MiB+1 byte payload → HTTP 400 {"error":"code_too_long","message":"code exceeds 4194304 bytes (got 4194305)"}

(5)DEFECT-L9 --- 轮询间隔 ~500ms(UI-SUB-008)

  • problem_detail.js:28 POLL_INITIAL_MS = 500
  • submission_detail.js:17 POLL_INITIAL_MS = 500
  • cancel-button.test.js:151-156 取消测试断言改为「cancel 后 getSubCalls 不再增长」(旧测试是固定值 1,按 1000ms 间隔校准)

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证
  • 第 8 节缺陷回归验证全部完成 ✅
缺陷 内容 验证结果
L5 历史相对时间 ✅ PASS--- /submissions 每行显示 45 分钟前/8 小时前 等相对时间(utils.formatRelativeTime)
L6 详情页语法高亮 ✅ PASS
L7 轮询失败重试按钮 ✅ PASS --- 拦截 GET → 重试 按钮出现,点击后恢复轮询并正常判题;详情页按钮也存在(默认隐藏)
L8 超长代码中文提示 ✅ PASS --- 捕获到 toast 「代码超过最大长度」(type=error),API 返回 code_too_long 正确映射
L9 轮询间隔 500ms ✅ PASS --- 实测间隔 509/504/512/508ms,5 次后退避至 2s(符合设计)

回归检查: L2(Markdown 渲染)、L4(草稿 localStorage 持久化)均正常,L6/L7 在详情页同样生效。

5.7 判题状态展示 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "9. 判题状态展示"

第 9 节「判题状态展示」测试结果

用例 sid 状态 结果
UI-VERDICT-001 AC #1327 verdict--ac 绿色 rgb(34,197,94) / "通过" / 文案「恭喜!全部测试用例通过。」不显示 error_msg ✅ PASS
UI-VERDICT-002 WA #1328 verdict--wa 红色 rgb(239,68,68) / "答案错误" ⚠️ PARTIAL
UI-VERDICT-003 TLE #1329 verdict--tle 黄色 rgb(245,158,11) / "运行超时" / 用时 1998ms (≈TL 2000) ✅ PASS
UI-VERDICT-004 RE #1331 verdict--re 红色 / "运行时错误" ⚠️ PARTIAL
UI-VERDICT-005 CE #1332 verdict--ce / 完整 g++ stderr 在 ⚠️ PARTIAL
UI-VERDICT-006 Pending --- 用 route mock 持续返回 Pending ❌ FAIL
UI-VERDICT-007 SE mock verdict--se 红色 / "系统错误" ⚠️ PARTIAL

详细问题点

  1. UI-VERDICT-002 WA:error_msg 格式不符规范。当前显示 Case 1 WA: Output mismatch (mode=Exact): first byte diff at 1:1(单行 diff 报告),没有规范要求的 Case k failed: / Expected: / Got: 三段式 diff。

  2. UI-VERDICT-004 RE:error_msg 只有 Case 1 RE,缺少信号名 (SIGSEGV) 或退出码信息。

  3. UI-VERDICT-005 CE:两个偏离:

    • 颜色是紫色 rgb(168, 85, 247)(class verdict--ce),规范要求红色
    • g++ stderr 中 error: token 没有加粗/红色高亮(纯文本)
  4. UI-VERDICT-006 Pending spinner ❌:

    • 页面没有 spinner + "Pending"/"Judging" 文案的元素
    • 初始加载时显示 submission-detail__skeleton(3 个 skeleton-line 灰色脉冲动画)
    • 数据到达后,如果 status 一直是 Pending,10 秒后自动回退为 SE,显示「已等待超过 10 秒未返回结果,自动标记为超时。后台判题仍在进行,可稍后刷新查看。」
    • CSS 中有 .verdict--pending 类定义,但没有任何 DOM 节点使用
  5. UI-VERDICT-007 SE:

    • 文案显示原始 error_msg judge worker crashed(mock 注入值)
    • 真实 SE 走 10s 超时分支时显示「已等待超过 10 秒未返回结果...」
    • 没有规范要求的「判题服务异常,请稍后重试」友好文案

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(执⾏ "9. 判题状态展示"),发现了以下缺陷:
	1. UI-VERDICT-002 WA:error_msg 格式不符规范。当前显示 Case 1 WA: Output mismatch (mode=Exact): first byte diff at 1:1(单行 diff 报告),没有规范要求的 Case k failed: / Expected: / Got: 三段式 diff。
	2. UI-VERDICT-004 RE:error_msg 只有 Case 1 RE,缺少信号名 (SIGSEGV) 或退出码信息。
	3. UI-VERDICT-005 CE:两个偏离:
		- 颜色是紫色 rgb(168, 85, 247)(class verdict--ce),规范要求红色
		- g++ stderr 中 error: token 没有加粗/红色高亮(纯文本)
	4. UI-VERDICT-006 Pending spinner ❌:
		- 页面没有 spinner + "Pending"/"Judging" 文案的元素
		- 初始加载时显示 submission-detail__skeleton(3 个 skeleton-line 灰色脉冲动画)
		- 数据到达后,如果 status 一直是 Pending,10 秒后自动回退为 SE,显示「已等待超过 10 秒未返回结果,自动标记为超时。后台判题仍在进行,可稍后刷新查看。」
		- CSS 中有 .verdict--pending 类定义,但没有任何 DOM 节点使用
	5. UI-VERDICT-007 SE:
		- 文案显示原始 error_msg judge worker crashed(mock 注入值)
		- 真实 SE 走 10s 超时分支时显示「已等待超过 10 秒未返回结果...」
		- 没有规范要求的「判题服务异常,请稍后重试」友好文案
请进行修复

修复了「9. 判题状态展示」的 5 个缺陷。

  1. UI-VERDICT-002 WA 三段式 diff

    • 后端 real_judge_worker.cpp:370-385:WA 用 comparator 的 expected_preview/actual_preview 拼成对齐的三段式:
      Case 1 failed: / Expected: 1 3 / Got: WRONG
    • 现场验证:真实判题输出已变为三行 diff。
  2. UI-VERDICT-004 RE 信号名/退出码

    • 新增 SignalName()(SIGSEGV/SIGABRT/SIGFPE/SIGKILL/SIGXCPU/SIGXFSZ 映射)。
    • real_judge_worker.cpp:341-360:RE 现在输出 Case 1 RE: Runtime error (signal=SIGSEGV);无信号时回落退出码。
  3. UI-VERDICT-005 CE 红 + error 高亮

    • base.css --v-ce: #A855F7(紫) → #EF4444(红);pages.css、components.css 的 CE 徽章从 rgba(168,85,247,...) 全部改红。
    • 前端 renderDiagnosticHtml():CE 的 error:/warning: 行包进 .diag-line--err/--warn(红粗/橙),WA/RE 的 Expected:/Got: 行用 .diag-line--exp/--got 区分高亮------UI 规范的"Expected/Got 肉眼对齐"。
  4. UI-VERDICT-006 Pending spinner + 文案 + 超时回退

    • VERDICT_ICONPending/Judging 已有 status-spinner + .verdict--pending 类;渲染时骨架隐藏、main 显示、spinner 出现。
    • Pending 超 10s 自动降级为 SEVER 展示(displayStatus() 已有逻辑),显示「已等待超过 10 秒未返回结果,自动标记为超时...」。
  5. UI-VERDICT-007 SE 友好文案

    • defaultVerdictMsg('SE') 改为「判题服务异常,请稍后重试。」
    • DIAGNOSTIC_STATUSES 移除 SE:SEVER 不再泄露内部 error_msg/judge worker crashed;verdict 卡只显示友好文案,diag 面板隐藏。

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证

第 9 节缺陷回归验证全部通过。/

用例 修复前 修复后
002 WA Case 1 WA: Output mismatch ...(单行) Case 1 failed: / Expected: hello world / Got: WRONG_OUTPUT(三段对齐) ✅
004 RE Case 1 RE(无信号) Case 1 RE: Runtime error (signal=SIGSEGV) ✅
005 CE 紫色 + 无 token 高亮 红色 rgba(239,68,68) + 3 行 error: 自动包 (红色 + bold 700) ✅
006 Pending skeleton-loader + 10s 后 fallback SE verdict--pending + (动画 verdict-spin)+ 文案「等待判题 / 提交已受理...」✅
007 SE 原始 error_msg 文案「判题服务异常,请稍后重试。」✅

5.7 管理员后台(CRUD) 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "10. 管理员后台(CRUD)"

第 10 节「管理员后台(CRUD)」测试结果

✅ PASS(18/21)

用例 结果
LIST-001 匿名→/login ✅ 跳 /login?next=%2Fadmin%2Fproblems,登录后回原页
LIST-002 alice→403 ✅ URL 不变,标题 "403 Forbidden",文案「该页面需要管理员权限」+ 「返回首页」
LIST-003 admin→列表 ✅ 23 题,每行含「编辑 / 删除」,顶部「+ 新建题目」
LIST-004 行内按钮 ✅ 23 个编辑 ,23 个删除
LIST-005 删除确认 ✅ 模态框「确认删除题目 #23」+ 取消/确认删除按钮
LIST-006 新建跳转 ✅ < a href="/admin/problems/new" >
DEL-002 取消则不删 ✅ 取消后无 DELETE 请求
NEW-001 表单字段 ✅ title/difficulty/tags/description/sample_in_out/tl/ml + 测试用例区
NEW-003 缺 title ✅「标题不能为空」
NEW-005 TL 越界 ✅「时间限制需在 1 - 60000 ms 之间」
NEW-006 ML 越界 ✅「内存限制需在 16 - 2048 MB 之间」
NEW-007 缺描述 ✅「题面不能为空」
NEW-008 用例增删 ✅ 1→4→3→4 行,索引重排无空行
NEW-009 用例 input 空 ✅ 阻止提交 + 定位具体行「测试用例 #2 的输入输出不能为空」
NEW-010 CSV tags ✅ 接受 math,dp,graph,详情页 3 个 badge
EDIT-001 数据回填 ✅ 全部字段 + 2 个 test_cases 加载(用 ?id= URL)
EDIT-002 全字段更新 ✅ PUT 200,所有字段持久化(API 验证)
EDIT-003 保留 test_cases ✅ 不动测试用例 → 原 2 条完整保留
EDIT-005 不存在 id ✅「题目 #99999999 不存在或已被删除」+ 「重试」
EDIT-006 取消按钮 ,无 PUT 请求
DEL-001 删除成功 ✅ 200 + 列表移除 + toast「已删除题目 #1933」
DEL-003 删除不存在 ✅ DELETE 99999999 → 404
DEL-004 级联删除 ✅ 5 条 submissions 全部级联清理

⚠️ 发现 3 个缺陷

  1. LIST-007/EDIT-001(URL 模式错配):列表编辑按钮跳 /admin/problems/:id/edit,但表单 JS id = parseInt(...location.search).get('id') 只读 ?id= 查询参数。访问 /admin/problems/23/edit 返回 200 但不发起 API 调用、数据不回填。实际工作的 URL 是 /admin/problems/new?id=23。
  2. NEW-002/EDIT-002(重定向错误):创建/编辑成功后跳转到 /admin/problems/new(空白新建表单)而非 /admin/problems(列表);无 toast「创建成功」/「保存成功」。
  3. EDIT-004(无法清空 test_cases):第一行「移除」按钮 disabled(防止减到 0 行),且空 input/output 行阻止提交,因此用户无法通过 UI 将 test_cases 数组清空。

(2)将测试过程中出现的缺陷,告知 Linux端的opencode,让其修复缺陷

  • 提示词:
bash 复制代码
@web_testcase.md 在基于 web_testcase.md ⽂档 进行web 自动化测试的过程中(执⾏ "10. 管理员后台(CRUD)"),发现了以下缺陷:
	1. LIST-007/EDIT-001(URL 模式错配):列表编辑按钮跳 /admin/problems/:id/edit,但表单 JS id = parseInt(...location.search).get('id') 只读 ?id= 查询参数。访问 /admin/problems/23/edit 返回 200 但不发起 API 调用、数据不回填。实际工作的 URL 是 /admin/problems/new?id=23。
	2. NEW-002/EDIT-002(重定向错误):创建/编辑成功后跳转到 /admin/problems/new(空白新建表单)而非 /admin/problems(列表);无 toast「创建成功」/「保存成功」。
	3. EDIT-004(无法清空 test_cases):第一行「移除」按钮 disabled(防止减到 0 行),且空 input/output 行阻止提交,因此用户无法通过 UI 将 test_cases 数组清空。
请进行修复

修复了 §10 「管理员后台(CRUD)」的 3 类共 5 个缺陷。

  1. LIST-007 / EDIT-001(URL 模式错配)

    • admin_problem_form.js:detectMode() 增加 parseProblemIdFromUrl():优先匹配路径 /admin/problems/:id/edit,回退到 ?id= 旧形式。
    • 现场验证:/admin/problems/23/edit 现在调 adminGetProblemFull(23) 并回填 title / difficulty / description / sample_io / TL / ML / tags / 2 行 test_cases("编辑题目 #23" 标题)。
    • 顺手修了另一处潜在缺陷:renderTestCases() 给 input/output textarea 加 value= 回填(之前只回填 score,新建带初始数据会显示空)。
  2. NEW-002 / EDIT-002(重定向 + toast)

    • onSubmit():create 成功后跳 /admin/problems(不再跳编辑页),toast「题目 #N 创建成功」;edit 成功后也跳 /admin/problems,toast「已保存」。均符合 web_testcase.md §10.1/§10.2「跳回列表」要求。
    • 新增 navigateTo(url) 导航漏斗:测试可通过 OJ.__adminFormNavigate 拦截跳转目标;线上仍然走 window.location.href。
    • 同时保留 toast「创建成功」措辞,匹配 spec。
  3. EDIT-004(可清空 test_cases)

    • renderTestCases() 的「移除」按钮在 edit 模式不再 disabled;新建仍要求 ≥1 行。
    • onTcListClick() 用 canRemoveLastRow() 守卫,与渲染一致。
    • validate() 仅 create 模式校验"≥1 用例";edit 允许空数组(服务端 PUT /api/admin/problems/:id 已经接受 test_cases: \[\],本次 e2e 验证 test_cases count: 0)。
    • 新增 escapeAttr() 防止 input/output 回填时 XSS。

(3)回到 windows端的opencode(验证修复结果)

  • 提示词:
bash 复制代码
上述缺陷已全部被修复,进行验证

缺陷修复回归验证结果

# 缺陷 修复前 修复后
1 /admin/problems/:id/edit 不加载数据 URL 返回 200,但无 API 调用,字段全空 ✅ API /api/admin/problems/1/full 200,全部字段正确回填,2 个 test_cases 加载
2 创建/编辑后错误跳转 跳 /admin/problems/new(空白新建页),无 toast ✅ 跳 /admin/problems(列表);创建 toast「题目 #2116 创建成功」、编辑 toast「已保存」
3 UI 无法清空 test_cases 第一行「移除」按钮 disabled,空行阻止提交 ✅ 移除按钮可点,逐行删除后 0 行可提交,API 保存 test_cases: \[\]

3 个缺陷全部修复 ✅。第 10 节 21 个用例现已全部通过。

5.8 RBAC 与受限页跳转 + 端到端业务流程 + 非功能 / 兼容性 / 异常 UI 测试

(1)在 windows端的opencode(只负责进行 Web 自动化测试)

  • 提示词:
bash 复制代码
/playwright-cli @web_testcase.md 请基于 web_testcase.md ⽂档, 使⽤ --headed 模式, 继续 web ⾃动化测试.
注意, 每个操作之间保留 1s 的间隔, ⽅便我⾁眼验证效果. 请继续执⾏ "11. RBAC 与受限页跳转、12. 端到端业务流程 和 13. 非功能 / 兼容性 / 异常 UI"

Section 11 全部 PASS (5 cases)✅

Section 12 全部 PASS ✅

# 用例 结果
FLOW-001 注册→登录→提交→AC ✅ newuser123 → submission 1591 → 通过
FLOW-002 登录失败→重试 ✅ 「用户名或密码错误」→ 正确密码登录
FLOW-003 提交 30s 内拿到 AC ✅ 4.7 秒(实际 < 5s)
FLOW-004 注销后访问受保护页 ✅ → /login?next=%2Fsubmissions
FLOW-005 admin 建题→alice 提交 ✅ 2120 → submission 1596 → AC (1 ms / 5 MB)
FLOW-006 alice 访问 admin URL ✅ 403 页
FLOW-007 CE/WA/TLE/RE ✅ 4 条全部正确,徽章颜色、错误信息、列表均正确

Section 13 全部 PASS ✅

# 用例 结果
NF-001 首页 HTML < 100 KB ✅ 11.56 KB
NF-002 ace.js ~500 KB ✅ 442 KB(ace 419 + mode 19 + theme 4)
NF-003 无 JS 错误 ✅ 公开页面无 Uncaught/TypeError
NF-004 移动端可用 ✅ 列表单列 325px、详情可滚动、按钮可点击(navbar 12px 微溢)
NF-005 编辑器保留 ✅ oj:code:1 localStorage,刷新后内容恢复
NF-006 长 CE 输出滚动 ✅ < pre > overflow:auto,源代码 max-height:520px + 纵向滚动
NF-007 F5 保留筛选 ✅ /problems?difficulty=Easy&page=2 URL + 状态保留
NF-008 慢网络 spinner ✅ .verdict--pending + .status-spinner 可见,最终 AC

5.9 总结(生成 web自动化测试文档-playwright-cli.md

  • 提示词(windows端的opencode):
bash 复制代码
把本次会话中的所有操作 总结成⽂档 @web自动化测试文档-playwright-cli.md(方便我后续再进行web自动化测试)
  • 通过上面的一系列操作,我们就完成了 web 自动化测试的整个过程。

    回顾一下:

    • 技术上基于 playwright-cli 实现浏览器操作
    • 测试之前, 需要先根据项目代码,梳理出一个初步的测试文档.
    • 每一次都执行文档中的一部分用例,来 "趟一遍坑".
    • 所有用例都 "趟完一遍" 之后,可以根据总结出的文档,再次进行测试,进一步验证
  • 完成 web 自动化测试后,SPEC.md文档中的 "11.验收标准",其实也已经完成了.

最终生成的 web自动化测试文档-playwright-cli.md见链接:https://gitee.com/bacht/oj_-platform-based_on-vibecoding/blob/master/web自动化测试文档-playwright-cli.md

六、文档编写

1. 编写项目 README 和 DEPOLY

⼀个开源项目通常都需要有介绍文档和⼀个部署文档. 我们可以让 AI 帮我们生成文档,使项目看起来 "更专业".

  • 提示词:
bash 复制代码
@SPEC.md 请根据⽂档描述和代码仓库内容, 请编写出 DEPLOY.md (表⽰项⽬部署⽂档), 和 README.md (项⽬介绍⽂档)

最终生成的 DEPLOY.md见链接:https://gitee.com/bacht/oj_-platform-based_on-vibecoding/blob/master/DEPLOY.md
最终生成的 README.md见链接:https://gitee.com/bacht/oj_-platform-based_on-vibecoding/blob/master/README.md



相关推荐
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-8超速判断(C++语言实现)
开发语言·数据结构·c++·算法
程序员黑豆2 小时前
鸿蒙应用开发 @BuilderParam 使用教程:实现灵活的 UI 插槽
前端·harmonyos
机建狂魔2 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
万少2 小时前
用 TraeWork 给小孩做一个家庭工作台
前端·人工智能·后端
孙启超2 小时前
【AI应用开发】ReAct 原理是什么?和普通直接提问 LLM 差别在哪?
前端·人工智能·llm·agent·react·rag·ai应用开发
TunerT_TQ2 小时前
Valhalla 静态工程审阅 #018|MMCV 源码证据驱动评测【大厂开源基础设施特辑】
c++·开源·mfc·#计算机视觉·#商汤科技·#openmmlab·#静态源码审计
SynthWriter2 小时前
下班前接到急活?我用 TRAE Work 20 分钟搞定了一套完整页面原型
前端
minimoon_jojo2 小时前
Antd Table虚拟滚动详解
前端
颜进强2 小时前
Calude Code - 23 用 MCP 把 Jenkins 变成 AI 队友:一次对话完成自动化发布
前端·后端