执行测试用例(run-testcase)¶
目标:在运行中的 OBPM/MyApps 上,按 testcase 或测试需求描述,用 Playwright MCP 执行测试,结果写入 {workspace}/{App}.application/test-results/。
本技能**只执行测试并写报告**,不生成/改写业务 XML,不修改用例 Markdown 正文。
模板:USER.template.md、result-template.md。
DOM API:dom-api.md;注入脚本:scripts/inject-obpm-dom.js。
结果目录约定: {App} 取目标软件目录名(不含后缀),例如 simple.application →
{workspace}/simple.application/test-results/{yyyyMMdd-HHmmss}/。
{App} 优先来自用例/overview 的软件 name、其次 USER.md 身份的「应用」字段、再否则扫描到的 *.application 目录名(歧义则询问)。
何时触发¶
- 用户要执行测试 / 跑用例 / 自动化验收 / 按场景执行测试
- 提供 testcase 文件路径、用例编号,或测试需求描述
writing-testcase产出用例后要求自动执行
输入¶
| 来源 | 规则 |
|---|---|
| testcase 文档 | 读取 00-overview.md、场景-*.md 或用例编号;步骤与预期对齐文档 |
| 测试需求描述 | 无现成用例时可直接执行;须先走清单门禁 |
{workspace}/USER.md |
多身份账号;用例「角色/账号」优先按 id 匹配,其次角色/显示名;歧义时询问 |
| HOST | 默认 http://localhost:8888;执行前询问是否修改 |
前置检查¶
执行任何浏览器操作前,必须依次完成:
- Playwright MCP(
user-playwright)可用 - 询问 HOST:默认
http://localhost:8888,是否改为其它 {workspace}/USER.md:存在且至少一个完整身份;否则按 USER.template.md 询问并写入/编辑
每个身份必填字段:显示名 / 用户名 / 密码 / 角色 / 部门 / 应用 / 备注(id 为###标题,如applicant)- 确定执行清单(见「选例」)
- 询问失败策略(见「失败策略」)
硬门禁:未完成以上五项前,禁止任何浏览器操作。
逐项询问(硬规则)¶
需用户确认的问题**必须一个一个问**,禁止在同一条回复里并列多个待确认项。
| 顺序 | 问题 | 何时问 |
|---|---|---|
| 1 | HOST(默认 http://localhost:8888) |
Playwright 可用后立刻问;本轮只问 HOST |
| 2 | USER.md 补全/歧义(若需要) | HOST 确认后;字段齐全且无歧义则跳过,不发问 |
| 3 | 执行清单(选例门禁) | 上一步完成后;仅当清单尚未由用户明确指定时 |
| 4 | 失败策略(默认失败继续) | 清单确定后;本轮只问失败策略 |
提问格式(编号选项)¶
凡需用户做选择的问题,**必须**以编号选项列出,请用户回复数字(可多选时写清规则,如 1 或 2,3)。
格式要求:
规则:
- 一问一答:每条 Agent 回复最多提出 一个 需用户回答的问题,然后停止等待
- 编号选项:可选项一律
1.2.3.… 列出;默认项写在对应选项旁,如1. 使用默认 http://localhost:8888(默认) - 用户回复数字:采纳对应选项;回复原文(如完整 URL、用例编号)也有效,按语义匹配
- 不得打包:禁止把 HOST / 清单 / 失败策略合成一次多问题确认
- 可附带只读信息:清单摘要、用例表可放在选项之前,但本轮只收对「请选择」的答复
- 用户已在一句里答多项:可一并采纳并跳过已答项,继续问下一项未确认的
- 可跳过:该步已明确则不问,直接进入下一门禁
各门禁示例选项¶
HOST:
执行清单(场景级,示例):
请选择本次执行范围:
1. 全部已纳入 P0(N 条)(默认)
2. 仅控件相关(TXT/OPT/FILE/OTH)
3. 仅流程相关(FLOW/RBS/APR)
4. 自定义(回复 4 并写出场景名或 TC 编号)
请回复选项编号。
场景很多时,也可按场景编号列出(1. 文本类型控件 …),并说明:回复单个编号、或逗号分隔多选如 1,3,5,或 0 表示全部。
失败策略:
选例(混合)¶
| 输入 | 行为 |
|---|---|
| 明确文件/编号/「全部 P0」等可执行集合 | 生成清单后**仍须**单独确认失败策略(若尚未确认),再执行 |
| 笼统需求 / 仅 overview / 场景未勾选 | HOST/USER 就绪后,单独出编号选项清单 → 等用户回复编号 → 再单独问失败策略 |
清单应包含:用例编号、标题、优先级、所需身份。用户用编号选定后再进入下一步(失败策略)。
出清单时:本轮只请用户按编号选择范围,不要同时问 HOST 或失败策略。
失败策略¶
清单确定后**单独询问一次**(编号选项,不要与 HOST/清单同条发出):
- 失败继续(默认)— 记录 FAIL,继续后续用例
- 遇失败即停 — 当前用例失败后中止套件
未明确回答时采用默认「失败继续」,但仍须按编号选项询问一次。用户答完后才可开始浏览器操作。
工作流¶
确认 Playwright MCP
→ 【停】只问 HOST(默认 http://localhost:8888)→ 等用户答
→ 检查 USER.md(缺字段/歧义时【停】只问这一项 → 等用户答;否则跳过)
→ 解析输入 → 若清单未明确:【停】只出示/确认执行清单 → 等用户答
→ 【停】只问失败策略(默认失败继续)→ 等用户答
→ 创建 {App}.application/test-results/{yyyyMMdd-HHmmss}/ + screenshots/
→ 打开 {HOST}/static/signon/index.html 登录(登录页可用 MCP 直接操作)
→ 等待 {HOST}/static/portal/vue/index.html 主界面就绪
→ 扫描目标 *.application:python scripts/build_obpm_catalog.py → catalog.json
→ 注入 inject-obpm-dom.js → OBPMTest.loadCatalog(catalog)
→ 逐条:换身份(若需)→ 步骤用 name/id 调 OBPMTest(内部按 id 定位 DOM)→ 断言 → 截图
→ 写 summary.md(用例 >5 可拆 TC-*.md)→ 关闭 Playwright 浏览器 → 对话汇报
收尾:关闭浏览器(硬规则)¶
本轮**执行清单全部处理完毕**后(含:全部跑完、遇失败即停中止、或用户中断后已写完 summary),必须关闭 Playwright 浏览器,再做对话汇报。
- 调用 Playwright MCP:
browser_close(user-playwright) - 若有多个 tab:对当前页/会话执行 close;必要时
browser_tabs确认无残留业务页 - 时机:
summary.md已落盘之后、对话汇报之前 - 例外:仅当用户明确要求「保持浏览器打开以便人工查看」时可跳过,并在 summary 备注写明
中途「人工协助」暂停、等待用户答问时**不要**关浏览器;只有本轮套件结束才关。
环境 URL¶
| 用途 | URL |
|---|---|
| 默认 HOST | http://localhost:8888 |
| 登录页 | {HOST}/static/signon/index.html |
| 主界面(portal) | {HOST}/static/portal/vue/index.html |
身份切换¶
- 用例步骤或前置条件指定角色/账号时,从
USER.md解析对应身份 - 切换身份:退出当前会话 → 重新打开登录页 → 用新身份登录 → 重新注入
OBPMTest - 同一身份连续多条用例可复用会话,无需重复登录
步骤执行¶
- 将用例「操作步骤」映射为
OBPMTest.*调用或 MCP 浏览器操作 - 将「预期结果」映射为
expectText/expectField/getToast等断言 - 每步记录 OK/FAIL;失败步骤必须截图
- 关键断言步骤(通过时)建议截图留档
保存后重开回显(硬规则)¶
用例要求「关闭后再次打开该单据并核对回显」时,禁止**再次点击同一菜单叶子当「重开」——侧栏菜单会走 forms/{formId}/empty,得到**新 docId,字段为空,不是原单。
| 步骤 | 正确做法 |
|---|---|
| 保存前 | setField / getField 一律带 formHint(表单 name),避免同名字段 catalog 歧义 |
| 保存 | 优先 clickAction("保存", formHint);若 activity 的 parentForm 为空/歧义,点 .act-btns [title="保存"],以 toast「保存 成功」为准 |
| 记录 | 从 #dy_refreshObj 读取 docid、formid;从运行时 URL/菜单 API 记下 appId(形如 ~~.…~~) |
| 关闭 | 「关闭所有」或关闭当前主页签 |
| 重开 | OBPMTest.reopenDocument({ appId, formId, docId, menuId?, name? })(内部 OBPM.addTab + _select/docId) |
| 回显前 | 必须 waitForFormReady(已内置于 getField/setField/reopenDocument):等到目标 .baseField 挂载,不能只凭 docid 已出现 |
根因:重开后「字段 DOM 未找到」¶
OBPM.addTab / 打开表单
→ 页签壳 + #dy_refreshObj[docid] 先就绪
→ 表单模板异步渲染 .baseField#<fieldId>
→ 若此时立刻 getField/setField:catalog 能 resolve id,但 DOM 尚无
→ [OBPMTest].*: DOM not found for id "…"
结论: 不是 catalog 错、也不是字段没有 id;是**就绪竞态**。运行时字段挂在 .baseField 的 HTML id(常无 fieldid 属性)。处理:waitForFormReady({ fieldId | docId | formId }) 后再读写。
另:换菜单时若旧表单仍打开,仅判断「任意 .baseField」会假就绪,随后对目标表单字段 id 查找失败。openMenu 必须先 closeAllTabs,并按菜单 actionContent(formId)等待 #dy_refreshObj[formid] 匹配。
易混淆的其它错误(文案不同,勿当成 DOM 未找到):
| 现象 | 真实原因 |
|---|---|
field not found in catalog / ambiguous |
字段 name 冲突,缺 formHint 或字段名写错(如把界面标签「标题」当成 catalog name,实际是「文本」) |
menu item not found: "HTML编辑器" |
菜单扁平:分类下直接是「HTML编辑器_基础」叶子,无中间层;用叶子 title 或 openMenu("HTML编辑器_基础") |
| 重开后字段为空但无抛错 | 误点菜单新建了 empty 单,未带 docId 的 addTab |
弹层闭环(硬规则,防残留遮挡)¶
打开任何选择器/视图/确认类弹层后,必须在离开该步骤前闭环关闭,禁止带着未关闭弹层去做下一步或下一条用例。
适用控件(含但不限于):
| 控件 | 典型触发 | 闭环动作 |
|---|---|---|
| 视图选择框 | 「视图选中」等按钮 → dialog / 库存选择列表 |
勾选/点选一行 →「确认」(放弃则「取消」/关闭) |
| 用户选择框 | 用户占位/加号 → .user-dialog |
选用户 →「确认」 |
| 部门/树形部门 | 部门触发器 → 部门弹层 | 选部门 →「确认」 |
| 下拉/日期等 | Element Plus popper | 点选项或点空白关闭,确认 popper 消失 |
必须遵守:
- 开则必关:打开弹层后,下一步操作前用 snapshot / DOM 确认弹层已消失(无可见
.el-dialog/.el-drawer/[role=dialog]遮挡主表单) - 选择类必须真实选数据:视图/用户/部门等用例必须完成「打开 → 选中目标行/节点 → 确认 → 映射/显示值回填」;禁止只改旁路文本框(如「映射名称」)却不点选弹层数据后标 PASS
- 残留先清:每条用例**开始前**与**结束后**,若发现残留弹层,先点「取消」/「关闭」/遮罩关闭;关不掉则 FAIL 本条并按失败策略处理,禁止硬点被遮挡的菜单/保存
- PASS 门槛:选择类控件用例未完成选行确认与回填校验的,不得记 PASS(记 FAIL,或卡点时按「人工协助」暂停);备注可写细节,但**不能用备注掩盖未闭环步骤**
- 截图时机:关键结果截图须在弹层已关闭、主表单可见后拍摄
反例(禁止): 打开「视图选中」→ 未勾选行、未点确认 → 切菜单跑下一条 → 弹层挡住操作;或仅手填映射名称后标 PASS。
硬门禁(执行期)¶
- 未确认 HOST、USER.md 未就绪、执行清单未确定:**禁止**开始浏览器操作
- 未注入
window.OBPMTest前:禁止**用临时脆弱选择器操作业务控件(**登录页除外) - 选择器/API 失效时:暂停并按「人工协助」处理,禁止硬猜冒充通过
- **弹层未闭环前禁止**进入下一步/下一条用例(见「弹层闭环」)
注入与调用(catalog → id → DOM)¶
浏览器内脚本**无法**直接读本地 .application。必须先在本机扫目录拿 id,再注入加载:
- 确认目标软件目录
{workspace}/…/{App}.application(用例/用户指定;歧义则询问) - 生成目录:
python <skill>/scripts/build_obpm_catalog.py "<App>.application" catalog.json
(解析.form/.view/.activity/.menu等,提取资源与字段 id) - 注入
scripts/inject-obpm-dom.js,确认window.OBPMTest OBPMTest.loadCatalog(<catalog.json 内容>)—— 未加载禁止setField/clickAction等- 业务操作优先
OBPMTest.*(参数可用 name 或 id;内部 resolve → 按fieldid/id/activityid定位);登录页可用 MCP 直接操作 - 保存后重开回显:用
reopenDocument,禁止再次点菜单当重开;读写字段前依赖waitForFormReady(见「保存后重开回显」)
换身份重新登录后:须**重新注入**脚本并**再次 loadCatalog**(可用同一份 catalog.json)。
MCP 调用示例¶
# 本机
python …/build_obpm_catalog.py "D:/ws/LeaveOA.application" catalog.json
# 浏览器
browser_evaluate: 执行 inject-obpm-dom.js 全文
browser_evaluate: OBPMTest.loadCatalog(<catalog object>)
browser_evaluate: await OBPMTest.setField("文本", "验收001", "单行文本框_基础")
browser_evaluate: await OBPMTest.clickAction("保存", "单行文本框_基础")
browser_evaluate: await OBPMTest.reopenDocument({ appId, formId, docId })
browser_evaluate: await OBPMTest.expectField("文本", "验收001", "单行文本框_基础")
详细 API 见 dom-api.md。
OBPMTest 能力概览¶
| 模块 | API |
|---|---|
| 目录 | loadCatalog / getCatalog / resolveField / resolveActivity / resolveMenu / resolveView / findDomById |
| 就绪 | ready() / waitForPortal(timeoutMs?) / waitForFormReady({fieldId?,docId?,timeoutMs?}) |
| 菜单 | openMenu(pathOrName)(优先叶子 [title];中间层可缺省) |
| 重开 | reopenDocument({appId,formId,docId,menuId?,name?}) |
| 表单字段 | setField(nameOrId, value, formHint?) / getField(…)(内置等字段 DOM) |
| 表单操作 | clickAction(nameOrId, parentHint?)(保存可回退到 .act-btns [title]) |
| 视图 | viewSearch / viewClickRow / viewClickToolbar |
| 流程/待办 | openTodo / flowAction |
| 断言 | expectText / expectField / getToast |
API 失败消息以 [OBPMTest] 开头;找不到 id 对应 DOM 时先确认是否未等表单字段就绪,再按「人工协助」校准 ID_ATTRS。
人工协助(DOM 构建/校准)¶
实现或执行过程中,选择器不确定或 API 失效时:
- 说明卡点:哪个 API、哪一步、当前页面 URL
- 请求协助:请用户在 DevTools 中提供控件稳定属性/HTML 片段,或先操作到目标页(表单/视图/待办)再继续
- 回写约定:将确认的 DOM 约定更新到
scripts/inject-obpm-dom.js的SELECTORS与 dom-api.md - 禁止硬猜:不要在选择器不确定时假装 API 可用或通过断言
用户/部门/视图选择器等复杂控件若首版策略跑不通,在结果备注中注明,并请求人工校准后重试;**不得**在弹层未确认关闭时继续后续用例,也不得用旁路字段填写冒充选择成功。
报告¶
按 result-template.md 写入 {workspace}/{App}.application/test-results/{runId}/。
目录结构¶
{workspace}/{App}.application/test-results/{yyyyMMdd-HHmmss}/
├── summary.md # 总览(必写)
├── TC-{编号}.md # 分用例详情(用例 >5 条时建议)
├── catalog.json # 可选:本轮 build_obpm_catalog 产出
└── screenshots/
└── {用例编号}-step{序号}-{简述}.png
示例:C:\work\trunk2018\storage\workspace\simple.application\test-results\20260731-194654\
summary.md 必含¶
- HOST、runId、开始/结束时间
- 失败策略、使用的身份列表
- 输入来源(用例路径 / 需求摘要)
- 结果表:编号、标题、结果(PASS/FAIL/SKIP)、身份、截图路径、失败摘要
- 统计:PASS / FAIL / SKIP / 合计
- 备注:环境/数据问题;DOM API 校准记录(无则写「无」)
截图命名¶
screenshots/{用例编号}-step{序号}-{简述}.png
- 失败步骤**必须**截图
- 通过用例的关键断言步骤**建议**截图
不修改 testcase 目录下的用例 Markdown 正文;执行结论只写 {App}.application/test-results/。
写完 summary.md 后按「收尾:关闭浏览器」调用 browser_close,再汇报。
与 writing-testcase¶
需求/SRS
→ writing-testcase → testcase/00-overview.md + 场景-*.md
↓
run-testcase 读取并执行
↓
{App}.application/test-results/{runId}/summary.md
writing-testcase产出用例文档 → 本技能读取并执行 → 落盘{App}.application/test-results/- 也可直接吃**测试需求描述**(不强制先写用例文档),此时走清单门禁后直接执行
- 两技能可串联使用,互不阻塞其它 generate-* 技能
不做¶
- 生成/改写业务 XML(
.form/.view/.flow等) - 修改用例 Markdown 正文(
testcase/下文档) - 在技能仓库内存放真实密码(密码只在 workspace 的
USER.md) - 设计器拖拽、报表像素断言、移动端 H5 全量、第三方 iframe 深页(首版范围外)
- 替代缺陷管理系统;不写 iScript/SQL 自动化(除非用例步骤明确要求验脚本效果且可经 UI 观察)