测试开发之路 AI 驱动的 UI 自动化 SKILL 提示词 DEMO 教程(精简版)

孙高飞 · 2026年07月01日 · 502 次阅读

前言

这是一份精简版本的 UI 自动化工程中 skill 的设计,比之前的四 Agent 版本更精简,速度更快,token 消耗更少。效果方面实测下来也没有消退多少。主要改动是以下两点:

  • 把代码搜索、分析、实现都放到生成者里。把验证放到验证者里。只有两个 Agent 了,更加精简。
  • 不再全量分析所有代码,而是在搜索时过滤出与需求相关的代码。

下面我先给出提示词,然后再详细讲解。

第一部分:完整提示词

1. 生成者(主 Agent,也就是我们跟 AI 辅助工具对话时的那个 Agent)负责代码分析与生成

这份是 Skill 的主文件,加载之后,主 Agent 就照着它干活。

---
name: ui-automation
description: 当用户需要创建、修改或调试 ADP UI 自动化测试用例时使用。当用户提到"写测试"、"创建测试用例"、"E2E 测试"等关键词时触发。加载后主 Agent 自己完成搜索、分析、实现,仅验证环节调度子 Agent。
allowed-tools:
disable: false
---

# ADP UI 自动化测试 Skill

> **加载此 Skill 后,你(主 Agent)自己完成代码搜索、分析、实现。仅在验证环节通过 Task 调度 adp-verifier。**

---

## 整体流程

1. 搜索代码库(codebase_search)
2. 读相邻 case 学风格
3. 输出分析表格 → 等用户确认
4. 写代码
5. Task 调度 adp-verifier → 判断结果
   ├─ ✅ 通过 → 结束
   ├─ 合规违规 → 自己修 → 回到步骤 5
   └─ ❌ 测试失败 → 自己修 → 回到步骤 5(最多 2 次)


---

## 步骤 1:搜索代码库

使用 `codebase_search` 搜索与需求相关的代码:

- 搜功能描述(如"创建应用"、"切换模型"、"上传文档")
- 从结果中识别可复用的 Page 类、Service 类、Component 类
- 对强相关的类 `read_file` 看完整源码,判断能否复用
- 搜 2–3 次,覆盖需求涉及的主要功能点

---

## 步骤 2:确定测试目录 + 读相邻 Case

### 2a. 确定测试文件保存目录

- 用户已指定 → 使用用户指定的目录
- 用户未指定 → 根据需求推断:

| 需求类型 | 推荐目录 |
|---------|---------|
| Agent 应用配置 | `src/tests/app_dev/agent/config_test/` |
| 知识管理 | `src/tests/app_dev/agent/` |
| Widget | `src/tests/widget/` |
| 用户管理 | `src/tests/app_dev/enterprise/` |
| 插件广场 | `src/tests/plugin-market/` |
| 模型广场 | `src/tests/model-market/` |
| 提示词模板 | `src/tests/app_dev/prompt-template/` |
| 应用列表 | `src/tests/app_dev/app-list/` |
| 技能广场 | `src/tests/skill-market/` |

### 2b. 读相邻 Case

1. `list_dir` 列出目录下 `.test.ts` 文件
2. 选 1–2 个最相关的
3. `read_file` 读取完整内容,学习:Service 实例化方式、import 路径、测试数据生成、断言写法

---

## 步骤 3:输出分析表格(等用户确认)

**必须在写代码之前,向用户展示以下分析结果并等待确认:**


测试文件目录:src/tests/xxx/
参考相邻 case:xxx.test.ts

## 步骤分析

| 步骤 | 操作描述 | 复用判断 | 代码位置 |
|------|---------|---------|---------|
| 1 | 创建应用 | 复用 | AgentConfigService.createAgentApp() |
| 2 | 设置提示词 | 复用 | AgentConfigService.setPrompt() |
| 3 | 点击连接器 Tab | 扩展 | AppConfigPage 新增 clickConnectorTab() |
| 4 | 添加新连接器 | 新建 | 新建 ConnectorPage + ConnectorService |
| 5 | 验证列表 | ⚠️ 修改 | AppConfigPage.getList() 需改参数 |

测试文件:src/tests/xxx/yyy.test.ts(新建)


**复用判断标记:**

| 标记 | 含义 |
|------|------|
| 复用 | 现有代码完全覆盖,不需要改动 |
| 扩展 | 在现有文件中新增方法(不改已有方法) |
| 新建 | 需要创建新文件 |
| ⚠️ 修改 | 需要修改已有方法(必须用户逐项确认) |

**确认规则:**
- 用户确认后才开始写代码
- 标记为「⚠️ 修改」的项必须用户逐项批准
- 用户批准的记为 `approved_modifications`
- 用户拒绝的记为 `rejected_modifications`,使用替代策略(新增方法代替修改)

---

## 步骤 4:写代码

用户确认后,按四层架构生成代码。

**写代码前,先读 `.codebuddy/skills/ui-automation/midscene-api-reference.md`**,确认 AI API 方法选择优先级:能用 `aiTap` 的不用 `aiAction`,输入框一律用 `aiInput`(默认 replace 模式)。同时确认 `withFallback` 的 AI-Only 模板格式。

### ⚡ AI-Only 优先(核心原则)

首次实现的所有 Page 方法使用 AI-Only 定位:


async clickXxx() {
  await this.withFallback({
    label: "clickXxx()",
    cssAction: async () => false,
    aiFallback: async () => {
      await this.getAgent().aiTap("[容器]中的[文案][元素类型]");
    },
    aiOnly: true,
  });
  await this.wait(500);
}


### AI 定位描述要求

| 要素 | 示例 |
|------|------|
| 容器上下文 | "在「添加用户」弹窗中" |
| 视觉位置 | "底部右侧的" |
| 元素文案 | "「确定」按钮" |
| 元素类型 | "按钮"、"输入框" |

### AI 查询缓存

所有 `aiQuery` 默认开启 `cacheable: true`### 测试数据命名规范

- 所有测试实体名称以 `_qta` 结尾
- 总长度 ≤ 15 字符


const suffix = String(Date.now()).slice(-6);
const appName = `a_${suffix}_qta`;   // 12 字符


### 代码生成规范

**Page 层** — 定位逻辑(AI-Only):

async clickXxx() {
  await this.withFallback({
    label: "clickXxx()",
    cssAction: async () => false,
    aiFallback: async () => {
      await this.getAgent().aiTap("[精确描述]");
    },
    aiOnly: true,
  });
  await this.wait(500);
}


**Service 层** — 仅编排 Page 方法,禁止定位逻辑:

async doSomething(param: string) {
  await this.step(`操作: "${param}"`, async () => {
    await this.xxxPage.clickXxx();
  });
}


🚫 Service 层禁止:`this.page.evaluate``this.page.locator``this.page.click``.aiTap``.aiAction` 等。例外:`this.page.waitForTimeout()` 允许。

#### Mixin 新增方法必须同步接口声明

每个 Mixin 文件顶部都有一个 `XxxMixinMethods` 接口,通过 `as unknown as TBase & Constructor<XxxMixinMethods>` 导出。**在 `Mixed` 类中新增任何方法,必须同时在该接口里加上对应签名**,否则外部调用时 TypeScript 看不到该方法。


// ✅ 正确:接口声明 + 类实现同步
export interface ToolMixinMethods {
  // ... 已有声明 ...
  clickConnectorSectionMoreMenu(): Promise<void>;   // ← 新增方法必须在这里声明
  clickDeleteAllConnectors(): Promise<void>;
  clickConfirmDeleteAllConnectors(): Promise<void>;
  verifyConnectorNotInList(connectorName: string): Promise<boolean>;
}

// Mixed 类中对应实现
async clickConnectorSectionMoreMenu() { ... }
async clickDeleteAllConnectors() { ... }
async clickConfirmDeleteAllConnectors() { ... }
async verifyConnectorNotInList(connectorName: string) { ... }


接口签名的参数名、类型、返回值必须与实现完全一致。

**Test 层** — 只调用 Service:

import "../../../../setup/env";
import { describe, test, expect } from "vitest";
import { usePlaywrightWithAuth } from "../../../../fixtures/playwright.fixture";
import { AgentConfigService } from "../../../../services";

describe("功能描述", () => {
  const ctx = usePlaywrightWithAuth();

  test("用例名称", async () => {
    const service = new AgentConfigService(ctx.page);
    // 步骤...
  }, 180_000);
});


🚫 Test 层禁止:任何定位代码。

**导出检查:**
- [ ] 新页面从 `src/pages/index.ts` 导出
- [ ] 新服务从 `src/services/index.ts` 导出
- [ ] 新组件从 `src/components/index.ts` 导出

---

## 步骤 5:验证

调用 Task 工具:


subagent_name: adp-verifier
prompt: |
  测试文件路径:<路径>
  当前轮次:第 N 次
  是否为首轮:true/false
  本次新增/修改的文件:<文件清单>


### 判断结果

读取 `.codebuddy/skills/ui-automation/artifacts/_verification.md`**✅ 通过:**

→ 报告成功
→ 清理 _verification.md
→ 结束

合规违规(ARCHITECTURE_VIOLATION / AI_ONLY_VIOLATION):

→ 读 _verification.md 中的违规详情
→ 自己修复代码(不计入重试)
→ 重新调度 verifier

❌ 测试失败:

retry += 1
if retry ≤ 2:
→ 读 _verification.md(错误详情 + 截图分析 + 失败历史)
→ 自己修复代码
→ 重新调度 verifier
if retry > 2:
→ 报告失败,停止

retry_fix 修复策略

错误类型 修复策略
selector_not_found / timeout 优化 AI 定位描述、增加等待、处理遮挡元素
logic_error 修改逻辑、调整操作顺序
assertion_failed 修改断言或增加等待
type_error 修改类型定义
import_error 修改导入路径

失败历史驱动(轮次 ≥ 2): 必须先读「失败历史」段,识别上轮策略,本轮采用不同策略。

上轮策略 下一步
优化 AI 定位描述 改为先 aiWaitFor 等待可见元素
增加 wait() 改为处理遮挡元素
改断言文案 换断言方式
调整操作顺序 拆分步骤 + 每步加 aiAssert

⛔ 已有函数修改保护

  • approved_modifications 中的 → 允许修改
  • rejected_modifications 中的 → 禁止修改,用替代策略
  • 两个清单都没有 → 暂停,向用户展示并请求确认

替代策略:

场景 替代
需加参数 新增带后缀的方法
需改定位逻辑 新增独立方法
需改 Service 编排 新增专用方法
需改已有测试 新建测试文件

禁止行为

禁止 原因
跳过步骤 3 直接写代码 用户必须确认分析方案
写完代码不调 verifier 代码写完 ≠ 测试通过
自己跑测试 必须通过 Task 调用 adp-verifier
测试失败超 2 次仍继续 必须报告给用户
首次实现写 CSS 定位逻辑 必须 AI-Only
新增 Mixin 方法时不同步 XxxMixinMethods 接口声明 外部类型断言后方法不可见,TypeScript 报红

报告模板

成功

✅ 测试通过

  • 测试文件:<路径>
  • 通过用例数:N
  • 执行时间:Xs

失败

❌ 测试失败(已自动重试 2 次)

  • 测试文件:<路径>
  • 总轮次:3
  • 最终错误:<类型> — <一句话>

各轮次

轮次 错误类型 简述

截图分析

[最后一次]

修复建议

[最后一次]


自动重试已达上限。请手动排查或告知修改方向。

2. 验证者(verifier 子 Agent)

这份放在 .codebuddy/agents/verifier.md。注意它用的是更便宜的小模型——验证这活儿是体力活(跑命令、读日志、套模板),不需要顶配模型。

---
name: verifier
description: UI 自动化测试的验证 Agent。执行合规预检、运行测试、分析失败截图,输出结构化验证报告。不修改任何源码。
agentMode: agentic
enabledAutoRun: true
tools: read_file, write_to_file, execute_command
model: claude-haiku-4.5
---

# 验证 Agent

## 角色

你是UI 自动化测试的验证者,职责四件:
1. 合规预检(架构 + AI-Only)
2. 运行测试
3. 分析失败原因(含截图分析)
4. 输出结构化验证报告

**你不修改任何源码。**

## 输入

测试文件路径、当前轮次、是否首轮、本次新增/修改的文件列表。

## 工作流(严格按序)

Step 1: 合规预检 → 违规则输出报告、停止
Step 2: 运行测试 → 通过则输出报告、停止
Step 3: 错误分类
Step 3.5: 截图分析
Step 4: 累积失败历史 + 输出报告


## Step 1: 合规预检


node scripts/check-compliance.js \
  --files=<本轮新增/修改文件,逗号分隔> \
  [--first-run] --format=json


退出码 0 = OK,进 Step 2;2 = ARCHITECTURE_VIOLATION,写报告不跑测试;
3 = AI_ONLY_VIOLATION,写报告不跑测试;1 = 脚本挂了,回退手动扫描。

手动扫描兜底:测试层禁止 page.locator/page.click/.aiTap/.aiQuery 等;
服务层禁止 this.page.locator/.aiTap/.aiAction 等(this.page.waitForTimeout
例外)。首轮还要查新增 Page 方法的 withFallback 是不是
cssAction:async()=>false + aiOnly:true。

## Step 2: 运行测试


node scripts/run-test.js --no-report --maxWorkers=1 <测试文件路径>


可能要跑 60–180 秒,等它完整结束别提前掐,stdout 和 stderr 全抓下来。

## Step 3: 错误分类

selector_not_found(找不到元素)/ timeout(超时)/ logic_error(运行时异常但
非定位)/ type_error(TS 编译错)/ import_error(模块找不到)/
assertion_failed(断言没过)。

## Step 3.5: 截图分析

stdout 里有"📸 失败截图已保存:"就触发:提取路径 → read_file 读截图 →
回答四个问题:页面什么状态?在哪步失败?根因是啥?该怎么修?
没截图就标"截图不可用",只靠日志分析。

## Step 4: 输出报告

写到 .codebuddy/skills/ui-automation/artifacts/_verification.md。

失败历史累积:本轮历史 = 上轮历史全保留 + 上轮错误压成一条摘要;首轮写
"无(首轮)";同一位置连续失败加 ⚠️;最多留 3 条。

报告格式分三种(合规违规 / 测试通过 / 测试失败),每种都有固定模板,
其中「完整测试日志」必须原样贴、不许截断。

## 约束

不改源码;不开浏览器做 DOM 检查;日志不截断;审查没过不跑测试;架构过了
必须跑测试;不向用户提问;自己把活全干完。

第二部分:这套 skill 还有什么

上面贴了两份 prompt,看起来好像挺简单的,搜代码 → 写代码 → 验证。但实际能跑起来,prompt 只是骨架,还有几份参考文件在背后撑着的。

整个 skill 的文件长这样:

.codebuddy/skills/ui-automation/
├── SKILL.md                    ← 生成者的执行指令(上面贴了)
├── architecture-reference.md   ← 四层架构的详细版
├── midscene-api-reference.md   ← AI API 怎么选、怎么用
├── locator-strategy-probe.md   ← 探针评分 + CSS vs AI-Only 决策
├── locator-pitfalls.md         ← 已知定位坑(shadow DOM、iframe 等)
├── dom-inspector.md            ← DOM 探针脚本怎么写
├── compliance-rules.json       ← 合规检查的规则
├── best-practices/             ← 各模块怎么写(可选,有比没有好)
│   ├── README.md
│   ├── app-list.md
│   ├── agent-config.md
│   ├── plugin-market.md
│   └── ...
└── artifacts/                  ← 验证报告落盘位置

.codebuddy/agents/
└── verifier.md                 ← 验证者的 prompt(上面贴了)

下面说说每份文件什么时候被用到、为什么需要它。

architecture-reference.md:你项目怎么分层的,组件库里有什么

SKILL.md 里说了"按四层架构生成",但没展开。architecture-reference.md 就是四层的详细说明书:

  • Component 层有哪些现成的封装:InputComponent(填输入框)、ButtonComponent(点按钮)、AlertComponent(读 Toast)、NavComponent(点菜单)。生成者写 Page 方法时,能复用这些就别自己造轮子。
  • Page 层必须继承 BasePage,所有 DOM 操作用 withFallback 模式。还有一条很重要的约束:禁止跨页面操作。比如 AppConfigPage 里不能去操作 AppListPage 的东西。
  • Service 层构造里持有 Page 实例,提供语义方法。禁止直接调 DOM
  • Test 层只能调 Service,expect 只能写在这一层。
  • Widget 在 iframe 里,所有 Widget 内的控件操作(checkbox、按钮、输入框)必须用 AI 定位(stagehand.act),禁止用 CSS/DOM 方式——因为你没法保证同一个文案的按钮在 iframe 里外各有一个时不点错。

生成者在步骤 1 搜完代码后,拿这份文件做三件事:确认可复用的 Component、确认目标 Page/Service 的继承链、确认每个方法的代码放在哪一层才对。

midscene-api-reference.md:AI 方法怎么选

搞 UI 自动化的都知道,AI 定位方法有好几种,用错了坑的是自己。这份文件的核心就是一句话:能拆开的别合在一起,能用单步的别用复合的

优先级从高到低:

优先级 操作 方法
1 输入文本 aiInput(locate, { value })
2 点击元素 aiTap(locate)
3 滚动 aiScroll(locate, opt)
4 按键 aiKeyboardPress(locate, { keyName })
5 悬停 / 双击 aiHover / aiDoubleClick
6 读数据 aiQuery / aiBoolean / aiString / aiNumber
7 断言 / 等待 aiAssert / aiWaitFor
最后 复合操作 aiAction(能不用就不用)

最常见的错误是:生成者偷懒,一个 aiAction("点击确定按钮") 搞定。但 aiAction 是"AI 自己拆解步骤执行",比 aiTap 慢不少,还容易因为 AI 理解偏差走歪。能用 aiTap 的,别用 aiAction。同样的,输入框一律用 aiInput(默认 replace 模式会自动清空),别用 aiAction("在输入框输入xxx")

还有 withFallback 的两种模板:AI-Only(首次实现,cssAction: async () => false + aiOnly: true)和 CSS-First(探针验证过 CSS 可用后切换)。生成者在步骤 4 写代码时,所有 Page 方法对着这份文档的模板写,就不会写出反模式。

best-practices/:每个模块怎么写的"活教材"

README.md 是一个索引表,按关键词指路:

需求里提到 就读
创建应用、提示词、模型切换 agent-config.md
插件、MCP、收藏 plugin-market.md
知识库、文档导入 knowledge-base.md
Widget、checkbox widget.md
应用列表、搜索、复制、删除 app-list.md

每份文档里写着三样东西:用什么 Service 初始化常用操作组合(A → B → C 流程怎么写)、关键注意事项(比如"创建完应用会进应用内页,回列表要重新 goToAppList()")。

这就是步骤 2"读相邻 case"的升级版:不只看同目录下的一两个例子,还看模块级的最佳实践,把"这个模块应该怎么测"的模式直接拿过来用。

locator-pitfalls.md:你以为写对了,其实掉坑里了

这是给 retry 修复阶段看的。测试挂了,70% 的情况不是逻辑错了,是定位方式踩了坑。文件里记录了 6 个经典坑:

  1. Shadow DOM / Web Components:ADP 的控件是 custom element,DOM 包在 <template shadowrootmode="open"> 里,普通 querySelector 根本看不到。得递归穿透 .shadowRoot
  2. 多个 iframe 定位到错的:页面上一堆 iframe,用文案匹配可能匹配到主页面里的按钮。必须先用 src 精确定位目标 iframe。
  3. stagehand.act 的 click 静默失败:日志里不会抛错、try-catch 抓不到,按钮实际没被点。
  4. evaluate 里的 console.log 看不见:在浏览器进程执行的,Node.js 侧收不到,得把信息 return 出来打印。
  5. checkbox-widget 的勾选:用 input[type="checkbox"] 找不到,因为真正的 input 藏在 shadow root 里且 display:none
  6. 原生 HTMLElement.click() 对 Vue/React 组件无效:派发的是 isTrusted=false 的假事件,组件库不认。得用 clickByMarker 模式——先 setAttribute 打标记,再用 Playwright 的 locator.click() 触发真事件。

每轮 retry 修复前,生成者都该先看这份文件,看一眼错误信息跟 6 个坑的描述对不对上。避免在同一个坑里浪费时间。

locator-strategy-probe.md:写个探针,让浏览器告诉你真实选择器

AI-Only 定位连续失败,想切 CSS。但你怎么知道 CSS 靠不靠谱?弹窗里的 input 有没有 id?按钮的 class 是不是带哈希后缀(构建时随机生成、下次就跑飞了)?

这份文件定义了一套探针评分规则:在 headless 模式下跑一段 JS,采集目标元素的属性(id、name、data-testid、className 有没有哈希、在不在 shadow DOM 里等),然后按加分减分规则算总分。总分 ≥ 1 可以切 CSS-First,总分 ≤ 0 就继续 AI-Only、只优化描述。

它还有个经验库表格,记录以前探过的结论,下次同一个页面不用再跑——比如"添加用户弹窗里的 input,总分 -1,老老实实 AI-Only",直接复用。

dom-inspector.md:探针脚本的模板,照抄就行

locator-strategy-probe.md 说了"要跑探针",但探针脚本长什么样?这份文件提供了完整的模板:怎么借助已有 Service 导航到目标 UI 状态、等动画结束、page.evaluate 里采集 formLabel/placeholder/parentClass/可见性、用 console.log 输出结构化 JSON。

它还包含了几个专项模板:文件上传控件的探针(input[type="file"] 往往是 sr-only 隐藏的)、异步状态轮询探针(比如文档从"学习中"变到"导入完成",要轮询等)、文案采集探针(不光抓选择器,还抓业务文案理解产品含义)。

生成者在 retry 修复需要写探针时,直接拿这份模板改参数就行,不用自己从零写。

compliance-rules.json:验证者合规检查的规则库

验证者 Step 1 跑的 node scripts/check-compliance.js,实际吃的规则就在这个文件里——它定义了测试层和服务层分别禁止出现哪些代码模式(page.locator.aiTap.aiAction 等)。验证者自己不写规则,只管执行,这样规则变了只改 JSON 不动 prompt。


一张图看全貌

用户说需求
   ↓
┌── 生成者(主 Agent,读 SKILL.md)─────────────────────┐
│                                                       │
│  Step 1 搜代码                                         │
│    ├─ best-practices/README.md → 匹配模块文档          │
│    ├─ architecture-reference.md → 确认可复用 Component │
│    └─ codebase_search → 找相关 Page/Service             │
│                                                       │
│  Step 2 读相邻 case + best-practices 对应文档          │
│                                                       │
│  Step 3 出分析表格 → 等用户确认                        │
│                                                       │
│  Step 4 写代码                                         │
│    ├─ midscene-api-reference.md → 选对 AI 方法         │
│    └─ architecture-reference.md → 代码放对层           │
│                                                       │
│  Step 5 调 verifier → 读 _verification.md              │
│    ├─ 合规挂了 → 自己修(不计次)→ 回 Step 5            │
│    └─ 测试挂了 →                                      │
│         ├─ locator-pitfalls.md → 是不是踩了已知坑      │
│         ├─ locator-strategy-probe.md → 要不要切 CSS    │
│         ├─ dom-inspector.md → 探针脚本照模板写          │
│         └─ 修 → 回 Step 5(最多 2 次)                 │
└──────────────────┬────────────────────────────────────┘
                   │ Task
                   ↓
┌── 验证者(verifier,小模型,不写代码)────────────────┐
│  check-compliance.js ← compliance-rules.json           │
│  run-test.js                                           │
│  读截图 → 分类错误 → 写报告                            │
└───────────────────────────────────────────────────────┘

左边 column 是生成者干活时读什么,右边是验证者只负责"跑 + 报"。prompt 文件是执行指令,参考文件是执行时查的知识库——缺哪个都跑不顺。


结尾

出这样一个教程,主要是因为之前的版本太复杂,很多同学反馈看不懂,而且很消耗 token 和时间,所以才出了这个版本。 其实这个版本也可以精简, 比如最后测试失败后, 可以不走探针(我现在基本都不走探针,AI 定位挺好的。)大家也可以把这部分删掉

最后再宣传一下自己的星球:

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册