跳转至

执行测试用例(run-testcase)

目标:在运行中的 OBPM/MyApps 上,按 testcase 或测试需求描述,用 Playwright MCP 执行测试,结果写入 {workspace}/{App}.application/test-results/

本技能**只执行测试并写报告**,不生成/改写业务 XML,不修改用例 Markdown 正文。

模板:USER.template.mdresult-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;执行前询问是否修改

前置检查

执行任何浏览器操作前,必须依次完成:

  1. Playwright MCPuser-playwright)可用
  2. 询问 HOST:默认 http://localhost:8888,是否改为其它
  3. {workspace}/USER.md:存在且至少一个完整身份;否则按 USER.template.md 询问并写入/编辑
    每个身份必填字段:显示名 / 用户名 / 密码 / 角色 / 部门 / 应用 / 备注(id 为 ### 标题,如 applicant
  4. 确定执行清单(见「选例」)
  5. 询问失败策略(见「失败策略」)

硬门禁:未完成以上五项前,禁止任何浏览器操作。

逐项询问(硬规则)

需用户确认的问题**必须一个一个问**,禁止在同一条回复里并列多个待确认项。

顺序 问题 何时问
1 HOST(默认 http://localhost:8888 Playwright 可用后立刻问;本轮只问 HOST
2 USER.md 补全/歧义(若需要) HOST 确认后;字段齐全且无歧义则跳过,不发问
3 执行清单(选例门禁) 上一步完成后;仅当清单尚未由用户明确指定时
4 失败策略(默认失败继续) 清单确定后;本轮只问失败策略

提问格式(编号选项)

凡需用户做选择的问题,**必须**以编号选项列出,请用户回复数字(可多选时写清规则,如 12,3)。

格式要求:

<简短说明,可选>

请选择:
1. <选项 A>(可标注默认)
2. <选项 B>
3. <选项 C>
请回复选项编号。

规则:

  • 一问一答:每条 Agent 回复最多提出 一个 需用户回答的问题,然后停止等待
  • 编号选项:可选项一律 1. 2. 3. … 列出;默认项写在对应选项旁,如 1. 使用默认 http://localhost:8888(默认)
  • 用户回复数字:采纳对应选项;回复原文(如完整 URL、用例编号)也有效,按语义匹配
  • 不得打包:禁止把 HOST / 清单 / 失败策略合成一次多问题确认
  • 可附带只读信息:清单摘要、用例表可放在选项之前,但本轮只收对「请选择」的答复
  • 用户已在一句里答多项:可一并采纳并跳过已答项,继续问下一项未确认的
  • 可跳过:该步已明确则不问,直接进入下一门禁

各门禁示例选项

HOST:

请选择 HOST:
1. 使用默认 http://localhost:8888(默认)
2. 使用其它地址(回复 2 并附上 URL)
请回复选项编号。

执行清单(场景级,示例):

请选择本次执行范围:
1. 全部已纳入 P0(N 条)(默认)
2. 仅控件相关(TXT/OPT/FILE/OTH)
3. 仅流程相关(FLOW/RBS/APR)
4. 自定义(回复 4 并写出场景名或 TC 编号)
请回复选项编号。

场景很多时,也可按场景编号列出(1. 文本类型控件 …),并说明:回复单个编号、或逗号分隔多选如 1,3,5,或 0 表示全部。

失败策略:

请选择失败策略:
1. 失败继续(默认)— 记录 FAIL,继续后续用例
2. 遇失败即停 — 当前用例失败后中止套件
请回复选项编号。

选例(混合)

输入 行为
明确文件/编号/「全部 P0」等可执行集合 生成清单后**仍须**单独确认失败策略(若尚未确认),再执行
笼统需求 / 仅 overview / 场景未勾选 HOST/USER 就绪后,单独出编号选项清单 → 等用户回复编号 → 再单独问失败策略

清单应包含:用例编号、标题、优先级、所需身份。用户用编号选定后再进入下一步(失败策略)。

出清单时:本轮只请用户按编号选择范围,不要同时问 HOST 或失败策略。

失败策略

清单确定后**单独询问一次**(编号选项,不要与 HOST/清单同条发出):

  1. 失败继续(默认)— 记录 FAIL,继续后续用例
  2. 遇失败即停 — 当前用例失败后中止套件

未明确回答时采用默认「失败继续」,但仍须按编号选项询问一次。用户答完后才可开始浏览器操作。

工作流

确认 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 浏览器,再做对话汇报。

  1. 调用 Playwright MCP:browser_closeuser-playwright
  2. 若有多个 tab:对当前页/会话执行 close;必要时 browser_tabs 确认无残留业务页
  3. 时机summary.md 已落盘之后、对话汇报之前
  4. 例外:仅当用户明确要求「保持浏览器打开以便人工查看」时可跳过,并在 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 读取 docidformid;从运行时 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编辑器_基础」叶子,无中间层;用叶子 titleopenMenu("HTML编辑器_基础")
重开后字段为空但无抛错 误点菜单新建了 empty 单,未带 docIdaddTab

弹层闭环(硬规则,防残留遮挡)

打开任何选择器/视图/确认类弹层后,必须在离开该步骤前闭环关闭,禁止带着未关闭弹层去做下一步或下一条用例。

适用控件(含但不限于):

控件 典型触发 闭环动作
视图选择框 「视图选中」等按钮 → dialog / 库存选择列表 勾选/点选一行 →「确认」(放弃则「取消」/关闭)
用户选择框 用户占位/加号 → .user-dialog 选用户 →「确认」
部门/树形部门 部门触发器 → 部门弹层 选部门 →「确认」
下拉/日期等 Element Plus popper 点选项或点空白关闭,确认 popper 消失

必须遵守:

  1. 开则必关:打开弹层后,下一步操作前用 snapshot / DOM 确认弹层已消失(无可见 .el-dialog / .el-drawer / [role=dialog] 遮挡主表单)
  2. 选择类必须真实选数据:视图/用户/部门等用例必须完成「打开 → 选中目标行/节点 → 确认 → 映射/显示值回填」;禁止只改旁路文本框(如「映射名称」)却不点选弹层数据后标 PASS
  3. 残留先清:每条用例**开始前**与**结束后**,若发现残留弹层,先点「取消」/「关闭」/遮罩关闭;关不掉则 FAIL 本条并按失败策略处理,禁止硬点被遮挡的菜单/保存
  4. PASS 门槛:选择类控件用例未完成选行确认与回填校验的,不得记 PASS(记 FAIL,或卡点时按「人工协助」暂停);备注可写细节,但**不能用备注掩盖未闭环步骤**
  5. 截图时机:关键结果截图须在弹层已关闭、主表单可见后拍摄

反例(禁止): 打开「视图选中」→ 未勾选行、未点确认 → 切菜单跑下一条 → 弹层挡住操作;或仅手填映射名称后标 PASS。

硬门禁(执行期)

  • 未确认 HOST、USER.md 未就绪、执行清单未确定:**禁止**开始浏览器操作
  • 未注入 window.OBPMTest 前:禁止**用临时脆弱选择器操作业务控件(**登录页除外
  • 选择器/API 失效时:暂停并按「人工协助」处理,禁止硬猜冒充通过
  • **弹层未闭环前禁止**进入下一步/下一条用例(见「弹层闭环」)

注入与调用(catalog → id → DOM)

浏览器内脚本**无法**直接读本地 .application。必须先在本机扫目录拿 id,再注入加载:

  1. 确认目标软件目录 {workspace}/…/{App}.application(用例/用户指定;歧义则询问)
  2. 生成目录:
    python <skill>/scripts/build_obpm_catalog.py "<App>.application" catalog.json
    (解析 .form / .view / .activity / .menu 等,提取资源与字段 id)
  3. 注入 scripts/inject-obpm-dom.js,确认 window.OBPMTest
  4. OBPMTest.loadCatalog(<catalog.json 内容>) —— 未加载禁止 setField / clickAction
  5. 业务操作优先 OBPMTest.*(参数可用 name 或 id;内部 resolve → 按 fieldid/id/activityid 定位);登录页可用 MCP 直接操作
  6. 保存后重开回显:用 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 失效时:

  1. 说明卡点:哪个 API、哪一步、当前页面 URL
  2. 请求协助:请用户在 DevTools 中提供控件稳定属性/HTML 片段,或先操作到目标页(表单/视图/待办)再继续
  3. 回写约定:将确认的 DOM 约定更新到 scripts/inject-obpm-dom.jsSELECTORSdom-api.md
  4. 禁止硬猜:不要在选择器不确定时假装 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 观察)