@creatoria/miniapp-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +469 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +144 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +73 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +118 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +50 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +189 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/core/element-ref.d.ts +44 -0
- package/dist/core/element-ref.d.ts.map +1 -0
- package/dist/core/element-ref.js +213 -0
- package/dist/core/element-ref.js.map +1 -0
- package/dist/core/logger.d.ts +55 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +378 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/output.d.ts +21 -0
- package/dist/core/output.d.ts.map +1 -0
- package/dist/core/output.js +56 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/report-generator.d.ts +24 -0
- package/dist/core/report-generator.d.ts.map +1 -0
- package/dist/core/report-generator.js +212 -0
- package/dist/core/report-generator.js.map +1 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +306 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/timeout.d.ts +49 -0
- package/dist/core/timeout.d.ts.map +1 -0
- package/dist/core/timeout.js +67 -0
- package/dist/core/timeout.js.map +1 -0
- package/dist/core/tool-logger.d.ts +83 -0
- package/dist/core/tool-logger.d.ts.map +1 -0
- package/dist/core/tool-logger.js +453 -0
- package/dist/core/tool-logger.js.map +1 -0
- package/dist/core/validation.d.ts +39 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +93 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +85 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/assert.d.ts +108 -0
- package/dist/tools/assert.d.ts.map +1 -0
- package/dist/tools/assert.js +291 -0
- package/dist/tools/assert.js.map +1 -0
- package/dist/tools/automator.d.ts +45 -0
- package/dist/tools/automator.d.ts.map +1 -0
- package/dist/tools/automator.js +186 -0
- package/dist/tools/automator.js.map +1 -0
- package/dist/tools/element.d.ts +253 -0
- package/dist/tools/element.d.ts.map +1 -0
- package/dist/tools/element.js +615 -0
- package/dist/tools/element.js.map +1 -0
- package/dist/tools/index.d.ts +97 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1565 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/miniprogram.d.ts +79 -0
- package/dist/tools/miniprogram.d.ts.map +1 -0
- package/dist/tools/miniprogram.js +245 -0
- package/dist/tools/miniprogram.js.map +1 -0
- package/dist/tools/network.d.ts +65 -0
- package/dist/tools/network.d.ts.map +1 -0
- package/dist/tools/network.js +205 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/page.d.ts +108 -0
- package/dist/tools/page.d.ts.map +1 -0
- package/dist/tools/page.js +307 -0
- package/dist/tools/page.js.map +1 -0
- package/dist/tools/record.d.ts +86 -0
- package/dist/tools/record.d.ts.map +1 -0
- package/dist/tools/record.js +316 -0
- package/dist/tools/record.js.map +1 -0
- package/dist/tools/snapshot.d.ts +82 -0
- package/dist/tools/snapshot.d.ts.map +1 -0
- package/dist/tools/snapshot.js +258 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/docs/SIMPLE_USAGE.md +210 -0
- package/docs/api/README.md +244 -0
- package/docs/api/assert.md +1015 -0
- package/docs/api/automator.md +345 -0
- package/docs/api/element.md +1454 -0
- package/docs/api/miniprogram.md +558 -0
- package/docs/api/network.md +883 -0
- package/docs/api/page.md +909 -0
- package/docs/api/record.md +963 -0
- package/docs/api/snapshot.md +792 -0
- package/docs/architecture.E-Docs.md +1359 -0
- package/docs/architecture.F1.md +720 -0
- package/docs/architecture.F2.md +871 -0
- package/docs/architecture.F3.md +905 -0
- package/docs/architecture.md +90 -0
- package/docs/charter.A1.align.yaml +170 -0
- package/docs/charter.A2.align.yaml +199 -0
- package/docs/charter.A3.align.yaml +242 -0
- package/docs/charter.A4.align.yaml +227 -0
- package/docs/charter.B1.align.yaml +179 -0
- package/docs/charter.B2.align.yaml +200 -0
- package/docs/charter.B3.align.yaml +200 -0
- package/docs/charter.B4.align.yaml +188 -0
- package/docs/charter.C1.align.yaml +190 -0
- package/docs/charter.C2.align.yaml +202 -0
- package/docs/charter.C3.align.yaml +211 -0
- package/docs/charter.C4.align.yaml +263 -0
- package/docs/charter.C5.align.yaml +220 -0
- package/docs/charter.D1.align.yaml +190 -0
- package/docs/charter.D2.align.yaml +234 -0
- package/docs/charter.D3.align.yaml +206 -0
- package/docs/charter.E-Docs.align.yaml +294 -0
- package/docs/charter.F1.align.yaml +193 -0
- package/docs/charter.F2.align.yaml +248 -0
- package/docs/charter.F3.align.yaml +287 -0
- package/docs/charter.G.align.yaml +174 -0
- package/docs/charter.align.yaml +111 -0
- package/docs/examples/session-report-usage.md +449 -0
- package/docs/maintenance.md +682 -0
- package/docs/playwright-mcp/350/260/203/347/240/224.md +53 -0
- package/docs/setup-guide.md +775 -0
- package/docs/tasks.A1.atomize.md +296 -0
- package/docs/tasks.A2.atomize.md +408 -0
- package/docs/tasks.A3.atomize.md +564 -0
- package/docs/tasks.A4.atomize.md +496 -0
- package/docs/tasks.B1.atomize.md +352 -0
- package/docs/tasks.B2.atomize.md +561 -0
- package/docs/tasks.B3.atomize.md +508 -0
- package/docs/tasks.B4.atomize.md +504 -0
- package/docs/tasks.C1.atomize.md +540 -0
- package/docs/tasks.C2.atomize.md +665 -0
- package/docs/tasks.C3.atomize.md +745 -0
- package/docs/tasks.C4.atomize.md +908 -0
- package/docs/tasks.C5.atomize.md +755 -0
- package/docs/tasks.D1.atomize.md +547 -0
- package/docs/tasks.D2.atomize.md +619 -0
- package/docs/tasks.D3.atomize.md +790 -0
- package/docs/tasks.E-Docs.atomize.md +1204 -0
- package/docs/tasks.atomize.md +189 -0
- package/docs/troubleshooting.md +855 -0
- package/docs//345/256/214/346/225/264/345/256/236/347/216/260/346/226/271/346/241/210.md +155 -0
- package/docs//345/274/200/345/217/221/344/273/273/345/212/241/350/256/241/345/210/222.md +110 -0
- package/docs//345/276/256/344/277/241/345/260/217/347/250/213/345/272/217/350/207/252/345/212/250/345/214/226API/345/256/214/346/225/264/346/226/207/346/241/243.md +894 -0
- package/docs//345/276/256/344/277/241/345/260/217/347/250/213/345/272/217/350/207/252/345/212/250/345/214/226/345/256/214/346/225/264/346/223/215/344/275/234/346/211/213/345/206/214.md +1885 -0
- package/docs//346/216/245/345/217/243/346/226/271/346/241/210.md +565 -0
- package/docs//347/254/254/344/270/200/347/211/210/346/234/254/346/226/271/346/241/210.md +380 -0
- package/package.json +87 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
# Record & Replay API
|
|
2
|
+
|
|
3
|
+
> Record 工具提供动作序列的录制、保存和回放功能,可以记录自动化操作并在后续重放,支持测试场景复用和回归测试。
|
|
4
|
+
|
|
5
|
+
## 工具列表
|
|
6
|
+
|
|
7
|
+
| 工具名称 | 描述 | 必需参数 |
|
|
8
|
+
|---------|------|----------|
|
|
9
|
+
| `record_start` | 开始录制动作序列 | 无 |
|
|
10
|
+
| `record_stop` | 停止录制并保存 | 无 |
|
|
11
|
+
| `record_list` | 列出所有已保存的序列 | 无 |
|
|
12
|
+
| `record_get` | 获取指定序列的详细信息 | sequenceId |
|
|
13
|
+
| `record_delete` | 删除指定序列 | sequenceId |
|
|
14
|
+
| `record_replay` | 回放已保存的序列 | sequenceId |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## record_start
|
|
19
|
+
|
|
20
|
+
开始录制自动化动作序列,录制期间所有工具调用都会被记录下来。
|
|
21
|
+
|
|
22
|
+
### 参数
|
|
23
|
+
|
|
24
|
+
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|
|
25
|
+
|--------|------|------|--------|------|
|
|
26
|
+
| `name` | string | ⭐ | `Recording {timestamp}` | 序列名称(便于识别) |
|
|
27
|
+
| `description` | string | ⭐ | - | 序列描述(说明用途或场景) |
|
|
28
|
+
|
|
29
|
+
### 返回值
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
{
|
|
33
|
+
success: true,
|
|
34
|
+
message: "Recording started: {name}",
|
|
35
|
+
sequenceId: "seq_1738425600000_abc123"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 错误处理
|
|
40
|
+
|
|
41
|
+
- **已在录制中**: `Error: Already recording. Stop current recording first.`
|
|
42
|
+
|
|
43
|
+
### 使用示例
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// 示例 1: 基础用法(使用默认名称)
|
|
47
|
+
const result = await record_start()
|
|
48
|
+
console.log(result.sequenceId) // "seq_1738425600000_abc123"
|
|
49
|
+
|
|
50
|
+
// 示例 2: 指定名称和描述
|
|
51
|
+
const result = await record_start({
|
|
52
|
+
name: "用户登录流程",
|
|
53
|
+
description: "测试用户登录并跳转到个人中心"
|
|
54
|
+
})
|
|
55
|
+
console.log(result.message) // "Recording started: 用户登录流程"
|
|
56
|
+
|
|
57
|
+
// 示例 3: 录制复杂测试场景
|
|
58
|
+
await record_start({
|
|
59
|
+
name: "购物车结算流程",
|
|
60
|
+
description: "从商品列表到下单完成的完整流程"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// 执行自动化操作(这些操作会被自动记录)
|
|
64
|
+
await miniprogram_navigate({ method: "navigateTo", url: "/pages/cart/cart" })
|
|
65
|
+
await element_tap({ selector: ".checkout-btn" })
|
|
66
|
+
// ... 更多操作
|
|
67
|
+
|
|
68
|
+
await record_stop() // 停止录制
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 注意事项
|
|
72
|
+
|
|
73
|
+
- ⚠️ **单录制限制**: 同一时间只能有一个录制会话,必须先停止当前录制才能开始新的录制
|
|
74
|
+
- 💡 **自动记录**: 录制开始后,所有工具调用(除了 record 工具本身)都会被自动记录
|
|
75
|
+
- 💡 **元数据记录**: 每个动作记录包含时间戳、工具名称、参数、执行结果、耗时等信息
|
|
76
|
+
- 💡 **命名规范**: 建议使用清晰的名称,如 "登录流程"、"商品搜索测试" 等
|
|
77
|
+
|
|
78
|
+
### 相关工具
|
|
79
|
+
|
|
80
|
+
- [`record_stop`](#record_stop) - 停止录制并保存
|
|
81
|
+
- [`record_replay`](#record_replay) - 回放录制的序列
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## record_stop
|
|
86
|
+
|
|
87
|
+
停止当前录制并保存动作序列到文件。
|
|
88
|
+
|
|
89
|
+
### 参数
|
|
90
|
+
|
|
91
|
+
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|
|
92
|
+
|--------|------|------|--------|------|
|
|
93
|
+
| `save` | boolean | ⭐ | true | 是否保存序列(false 则丢弃) |
|
|
94
|
+
|
|
95
|
+
### 返回值
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{
|
|
99
|
+
success: true,
|
|
100
|
+
message: "Recording stopped: {name} ({actionCount} actions)",
|
|
101
|
+
sequenceId: "seq_1738425600000_abc123",
|
|
102
|
+
actionCount: 15,
|
|
103
|
+
filePath: "/path/to/.mcp-artifacts/{sessionId}/sequences/seq_xxx.json"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 错误处理
|
|
108
|
+
|
|
109
|
+
- **未在录制**: `Error: Not currently recording`
|
|
110
|
+
- **文件保存失败**: `Error: Failed to stop recording: {error}`
|
|
111
|
+
|
|
112
|
+
### 使用示例
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// 示例 1: 基础用法(保存序列)
|
|
116
|
+
await record_start({ name: "登录测试" })
|
|
117
|
+
// ... 执行自动化操作
|
|
118
|
+
const result = await record_stop()
|
|
119
|
+
console.log(result.message) // "Recording stopped: 登录测试 (8 actions)"
|
|
120
|
+
console.log(result.filePath) // 序列文件路径
|
|
121
|
+
|
|
122
|
+
// 示例 2: 丢弃录制(不保存)
|
|
123
|
+
await record_start({ name: "临时测试" })
|
|
124
|
+
// ... 执行操作
|
|
125
|
+
await record_stop({ save: false })
|
|
126
|
+
// 不会生成序列文件
|
|
127
|
+
|
|
128
|
+
// 示例 3: 完整的录制流程
|
|
129
|
+
const session = await record_start({
|
|
130
|
+
name: "购物车操作",
|
|
131
|
+
description: "添加商品到购物车并结算"
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// 执行自动化操作
|
|
135
|
+
await miniprogram_navigate({ method: "navigateTo", url: "/pages/product/product" })
|
|
136
|
+
await element_tap({ selector: ".add-to-cart" })
|
|
137
|
+
await element_tap({ selector: ".cart-icon" })
|
|
138
|
+
await element_tap({ selector: ".checkout-btn" })
|
|
139
|
+
|
|
140
|
+
// 停止并保存
|
|
141
|
+
const result = await record_stop()
|
|
142
|
+
console.log(`已保存 ${result.actionCount} 个动作`)
|
|
143
|
+
console.log(`序列ID: ${result.sequenceId}`)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 注意事项
|
|
147
|
+
|
|
148
|
+
- ⚠️ **自动清理**: 停止后录制状态会被清除,无论是否保存
|
|
149
|
+
- ⚠️ **文件位置**: 序列文件保存在 `.mcp-artifacts/{sessionId}/sequences/` 目录
|
|
150
|
+
- 💡 **调试技巧**: 测试时可以设置 `save: false` 避免生成过多序列文件
|
|
151
|
+
- 💡 **序列ID**: 由时间戳和随机字符串组成,全局唯一
|
|
152
|
+
|
|
153
|
+
### 相关工具
|
|
154
|
+
|
|
155
|
+
- [`record_start`](#record_start) - 开始录制
|
|
156
|
+
- [`record_list`](#record_list) - 查看已保存的序列
|
|
157
|
+
- [`record_get`](#record_get) - 查看序列详情
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## record_list
|
|
162
|
+
|
|
163
|
+
列出当前会话中所有已保存的动作序列。
|
|
164
|
+
|
|
165
|
+
### 参数
|
|
166
|
+
|
|
167
|
+
无参数。
|
|
168
|
+
|
|
169
|
+
### 返回值
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
{
|
|
173
|
+
success: true,
|
|
174
|
+
message: "Found {count} sequences",
|
|
175
|
+
sequences: [
|
|
176
|
+
{
|
|
177
|
+
id: "seq_1738425600000_abc123",
|
|
178
|
+
name: "用户登录流程",
|
|
179
|
+
description: "测试用户登录并跳转到个人中心",
|
|
180
|
+
createdAt: "2025-02-01T10:00:00.000Z",
|
|
181
|
+
actionCount: 8
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "seq_1738426200000_def456",
|
|
185
|
+
name: "购物车结算流程",
|
|
186
|
+
createdAt: "2025-02-01T10:10:00.000Z",
|
|
187
|
+
actionCount: 15
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 错误处理
|
|
194
|
+
|
|
195
|
+
- **目录读取失败**: `Error: Failed to list sequences: {error}`
|
|
196
|
+
|
|
197
|
+
### 使用示例
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// 示例 1: 基础用法
|
|
201
|
+
const result = await record_list()
|
|
202
|
+
console.log(result.message) // "Found 5 sequences"
|
|
203
|
+
result.sequences.forEach(seq => {
|
|
204
|
+
console.log(`${seq.name}: ${seq.actionCount} 个动作`)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// 示例 2: 查找特定序列
|
|
208
|
+
const result = await record_list()
|
|
209
|
+
const loginSeq = result.sequences.find(seq =>
|
|
210
|
+
seq.name.includes("登录")
|
|
211
|
+
)
|
|
212
|
+
if (loginSeq) {
|
|
213
|
+
console.log(`找到登录序列: ${loginSeq.id}`)
|
|
214
|
+
await record_replay({ sequenceId: loginSeq.id })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 示例 3: 显示所有序列信息
|
|
218
|
+
const result = await record_list()
|
|
219
|
+
console.log(`共有 ${result.sequences.length} 个序列:\n`)
|
|
220
|
+
result.sequences.forEach((seq, index) => {
|
|
221
|
+
console.log(`${index + 1}. ${seq.name}`)
|
|
222
|
+
console.log(` ID: ${seq.id}`)
|
|
223
|
+
console.log(` 描述: ${seq.description || '无'}`)
|
|
224
|
+
console.log(` 动作数: ${seq.actionCount}`)
|
|
225
|
+
console.log(` 创建时间: ${new Date(seq.createdAt).toLocaleString()}`)
|
|
226
|
+
console.log()
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 注意事项
|
|
231
|
+
|
|
232
|
+
- 💡 **会话隔离**: 只列出当前会话目录下的序列
|
|
233
|
+
- 💡 **排序**: 序列按文件名排序(通常是时间顺序)
|
|
234
|
+
- 💡 **过滤**: 只读取 `.json` 文件,忽略其他文件
|
|
235
|
+
- 💡 **性能**: 如果序列很多,可能需要一定时间读取
|
|
236
|
+
|
|
237
|
+
### 相关工具
|
|
238
|
+
|
|
239
|
+
- [`record_get`](#record_get) - 获取序列详细信息
|
|
240
|
+
- [`record_delete`](#record_delete) - 删除序列
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## record_get
|
|
245
|
+
|
|
246
|
+
获取指定序列的完整详细信息,包括所有动作记录。
|
|
247
|
+
|
|
248
|
+
### 参数
|
|
249
|
+
|
|
250
|
+
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|
|
251
|
+
|--------|------|------|--------|------|
|
|
252
|
+
| `sequenceId` | string | ✅ | - | 序列ID(从 list 或 start/stop 获取) |
|
|
253
|
+
|
|
254
|
+
### 返回值
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
{
|
|
258
|
+
success: true,
|
|
259
|
+
message: "Sequence retrieved: {name}",
|
|
260
|
+
sequence: {
|
|
261
|
+
id: "seq_1738425600000_abc123",
|
|
262
|
+
name: "用户登录流程",
|
|
263
|
+
description: "测试用户登录并跳转到个人中心",
|
|
264
|
+
createdAt: "2025-02-01T10:00:00.000Z",
|
|
265
|
+
actions: [
|
|
266
|
+
{
|
|
267
|
+
timestamp: "2025-02-01T10:00:01.234Z",
|
|
268
|
+
toolName: "miniprogram_navigate",
|
|
269
|
+
args: { method: "navigateTo", url: "/pages/login/login" },
|
|
270
|
+
success: true,
|
|
271
|
+
duration: 150
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
timestamp: "2025-02-01T10:00:02.456Z",
|
|
275
|
+
toolName: "element_input",
|
|
276
|
+
args: { selector: "#username", value: "testuser" },
|
|
277
|
+
success: true,
|
|
278
|
+
duration: 80
|
|
279
|
+
},
|
|
280
|
+
// ... 更多动作
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 错误处理
|
|
287
|
+
|
|
288
|
+
- **序列不存在**: `Error: Failed to get sequence: ENOENT: no such file`
|
|
289
|
+
- **JSON 解析失败**: `Error: Failed to get sequence: Unexpected token`
|
|
290
|
+
|
|
291
|
+
### 使用示例
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// 示例 1: 基础用法
|
|
295
|
+
const result = await record_get({
|
|
296
|
+
sequenceId: "seq_1738425600000_abc123"
|
|
297
|
+
})
|
|
298
|
+
console.log(result.sequence.name) // "用户登录流程"
|
|
299
|
+
console.log(result.sequence.actions.length) // 8
|
|
300
|
+
|
|
301
|
+
// 示例 2: 分析序列内容
|
|
302
|
+
const result = await record_get({ sequenceId: "seq_xxx" })
|
|
303
|
+
const sequence = result.sequence
|
|
304
|
+
|
|
305
|
+
console.log(`序列名称: ${sequence.name}`)
|
|
306
|
+
console.log(`总动作数: ${sequence.actions.length}`)
|
|
307
|
+
|
|
308
|
+
// 统计工具使用
|
|
309
|
+
const toolStats = {}
|
|
310
|
+
sequence.actions.forEach(action => {
|
|
311
|
+
toolStats[action.toolName] = (toolStats[action.toolName] || 0) + 1
|
|
312
|
+
})
|
|
313
|
+
console.log("工具使用统计:", toolStats)
|
|
314
|
+
|
|
315
|
+
// 查找失败的动作
|
|
316
|
+
const failures = sequence.actions.filter(a => !a.success)
|
|
317
|
+
if (failures.length > 0) {
|
|
318
|
+
console.log("失败的动作:")
|
|
319
|
+
failures.forEach(f => {
|
|
320
|
+
console.log(`- ${f.toolName}: ${f.error}`)
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 示例 3: 提取序列步骤
|
|
325
|
+
const result = await record_get({ sequenceId: "seq_xxx" })
|
|
326
|
+
console.log("序列步骤:")
|
|
327
|
+
result.sequence.actions.forEach((action, index) => {
|
|
328
|
+
const time = new Date(action.timestamp).toLocaleTimeString()
|
|
329
|
+
const status = action.success ? "✅" : "❌"
|
|
330
|
+
console.log(`${index + 1}. [${time}] ${status} ${action.toolName}`)
|
|
331
|
+
if (action.duration) {
|
|
332
|
+
console.log(` 耗时: ${action.duration}ms`)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 注意事项
|
|
338
|
+
|
|
339
|
+
- 💡 **完整数据**: 返回序列的完整 JSON 数据,包括所有动作详情
|
|
340
|
+
- 💡 **时间戳格式**: 所有时间戳为 ISO 8601 格式字符串
|
|
341
|
+
- 💡 **成功标志**: `success: true/false` 标识动作执行结果
|
|
342
|
+
- 💡 **错误信息**: 失败的动作包含 `error` 字段(错误消息)
|
|
343
|
+
|
|
344
|
+
### 相关工具
|
|
345
|
+
|
|
346
|
+
- [`record_list`](#record_list) - 列出所有序列
|
|
347
|
+
- [`record_replay`](#record_replay) - 回放序列
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## record_delete
|
|
352
|
+
|
|
353
|
+
删除指定的动作序列文件。
|
|
354
|
+
|
|
355
|
+
### 参数
|
|
356
|
+
|
|
357
|
+
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|
|
358
|
+
|--------|------|------|--------|------|
|
|
359
|
+
| `sequenceId` | string | ✅ | - | 要删除的序列ID |
|
|
360
|
+
|
|
361
|
+
### 返回值
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
{
|
|
365
|
+
success: true,
|
|
366
|
+
message: "Sequence deleted: {sequenceId}"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 错误处理
|
|
371
|
+
|
|
372
|
+
- **序列不存在**: `Error: Failed to delete sequence: ENOENT: no such file`
|
|
373
|
+
- **文件删除失败**: `Error: Failed to delete sequence: Permission denied`
|
|
374
|
+
|
|
375
|
+
### 使用示例
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
// 示例 1: 基础用法
|
|
379
|
+
const result = await record_delete({
|
|
380
|
+
sequenceId: "seq_1738425600000_abc123"
|
|
381
|
+
})
|
|
382
|
+
console.log(result.message) // "Sequence deleted: seq_1738425600000_abc123"
|
|
383
|
+
|
|
384
|
+
// 示例 2: 批量删除旧序列
|
|
385
|
+
const { sequences } = await record_list()
|
|
386
|
+
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
|
|
387
|
+
|
|
388
|
+
for (const seq of sequences) {
|
|
389
|
+
const createdAt = new Date(seq.createdAt).getTime()
|
|
390
|
+
if (createdAt < oneWeekAgo) {
|
|
391
|
+
await record_delete({ sequenceId: seq.id })
|
|
392
|
+
console.log(`已删除旧序列: ${seq.name}`)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 示例 3: 删除前确认
|
|
397
|
+
const sequenceId = "seq_xxx"
|
|
398
|
+
const { sequence } = await record_get({ sequenceId })
|
|
399
|
+
|
|
400
|
+
console.log(`确定要删除序列 "${sequence.name}" 吗?`)
|
|
401
|
+
console.log(`包含 ${sequence.actions.length} 个动作`)
|
|
402
|
+
// 用户确认后...
|
|
403
|
+
await record_delete({ sequenceId })
|
|
404
|
+
console.log("已删除")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 注意事项
|
|
408
|
+
|
|
409
|
+
- ⚠️ **不可恢复**: 删除操作不可恢复,请谨慎使用
|
|
410
|
+
- ⚠️ **ID 精确匹配**: 必须提供完整的序列ID
|
|
411
|
+
- 💡 **批量清理**: 可以结合 `list` 实现批量删除旧序列
|
|
412
|
+
- 💡 **幂等性**: 删除不存在的序列会抛出错误(非幂等)
|
|
413
|
+
|
|
414
|
+
### 相关工具
|
|
415
|
+
|
|
416
|
+
- [`record_list`](#record_list) - 查看可删除的序列
|
|
417
|
+
- [`record_get`](#record_get) - 删除前查看详情
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## record_replay
|
|
422
|
+
|
|
423
|
+
回放已保存的动作序列,按顺序重新执行所有记录的工具调用。
|
|
424
|
+
|
|
425
|
+
### 参数
|
|
426
|
+
|
|
427
|
+
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|
|
428
|
+
|--------|------|------|--------|------|
|
|
429
|
+
| `sequenceId` | string | ✅ | - | 要回放的序列ID |
|
|
430
|
+
| `continueOnError` | boolean | ⭐ | false | 遇到错误时是否继续执行 |
|
|
431
|
+
|
|
432
|
+
### 返回值
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
{
|
|
436
|
+
success: true, // 所有动作都成功时为 true
|
|
437
|
+
message: "Replay completed: {successCount} success, {failureCount} failures",
|
|
438
|
+
totalActions: 15,
|
|
439
|
+
successCount: 14,
|
|
440
|
+
failureCount: 1,
|
|
441
|
+
results: [
|
|
442
|
+
{
|
|
443
|
+
toolName: "miniprogram_navigate",
|
|
444
|
+
success: true
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
toolName: "element_tap",
|
|
448
|
+
success: false,
|
|
449
|
+
error: "Element not found: .missing-btn"
|
|
450
|
+
},
|
|
451
|
+
// ... 更多结果
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 错误处理
|
|
457
|
+
|
|
458
|
+
- **序列不存在**: `Error: Failed to replay sequence: Failed to get sequence`
|
|
459
|
+
- **工具不存在**: `Error: Replay stopped at action {toolName}: Tool not found`
|
|
460
|
+
- **动作执行失败** (continueOnError=false): `Error: Replay stopped at action {toolName}: {error}`
|
|
461
|
+
|
|
462
|
+
### 使用示例
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
// 示例 1: 基础用法(遇错停止)
|
|
466
|
+
const result = await record_replay({
|
|
467
|
+
sequenceId: "seq_1738425600000_abc123"
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
if (result.success) {
|
|
471
|
+
console.log("序列回放成功!")
|
|
472
|
+
} else {
|
|
473
|
+
console.log(`回放失败: ${result.failureCount} 个动作失败`)
|
|
474
|
+
result.results.forEach((r, i) => {
|
|
475
|
+
if (!r.success) {
|
|
476
|
+
console.log(`动作 ${i + 1} (${r.toolName}) 失败: ${r.error}`)
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 示例 2: 持续执行模式(收集所有错误)
|
|
482
|
+
const result = await record_replay({
|
|
483
|
+
sequenceId: "seq_xxx",
|
|
484
|
+
continueOnError: true
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
console.log(`总计: ${result.totalActions} 个动作`)
|
|
488
|
+
console.log(`成功: ${result.successCount}`)
|
|
489
|
+
console.log(`失败: ${result.failureCount}`)
|
|
490
|
+
|
|
491
|
+
// 生成测试报告
|
|
492
|
+
console.log("\n测试报告:")
|
|
493
|
+
result.results.forEach((r, index) => {
|
|
494
|
+
const status = r.success ? "✅" : "❌"
|
|
495
|
+
console.log(`${index + 1}. ${status} ${r.toolName}`)
|
|
496
|
+
if (r.error) {
|
|
497
|
+
console.log(` 错误: ${r.error}`)
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
// 示例 3: 回归测试流程
|
|
502
|
+
async function regressionTest(sequenceId) {
|
|
503
|
+
console.log("开始回归测试...")
|
|
504
|
+
|
|
505
|
+
const result = await record_replay({
|
|
506
|
+
sequenceId,
|
|
507
|
+
continueOnError: true
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
const passRate = (result.successCount / result.totalActions) * 100
|
|
511
|
+
console.log(`通过率: ${passRate.toFixed(2)}%`)
|
|
512
|
+
|
|
513
|
+
if (result.failureCount > 0) {
|
|
514
|
+
console.log("\n失败的测试用例:")
|
|
515
|
+
result.results
|
|
516
|
+
.filter(r => !r.success)
|
|
517
|
+
.forEach(r => {
|
|
518
|
+
console.log(`- ${r.toolName}: ${r.error}`)
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return result.success
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 运行回归测试
|
|
526
|
+
const passed = await regressionTest("seq_xxx")
|
|
527
|
+
if (!passed) {
|
|
528
|
+
console.log("⚠️ 测试未通过,请检查上述错误")
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 注意事项
|
|
533
|
+
|
|
534
|
+
- ⚠️ **状态依赖**: 回放前确保环境状态符合序列录制时的状态(如页面路径、数据等)
|
|
535
|
+
- ⚠️ **时序问题**: 回放不会等待原录制的时间间隔,会连续执行
|
|
536
|
+
- ⚠️ **动态数据**: 如果序列包含动态数据(如时间戳、随机ID),回放可能失败
|
|
537
|
+
- 💡 **continueOnError**: 设为 `true` 时会执行完所有动作,适合测试覆盖率检查
|
|
538
|
+
- 💡 **错误定位**: `results` 数组与原 `actions` 数组一一对应,便于定位问题
|
|
539
|
+
- 💡 **性能**: 回放速度通常比原录制更快(无人工等待时间)
|
|
540
|
+
|
|
541
|
+
### 相关工具
|
|
542
|
+
|
|
543
|
+
- [`record_start`](#record_start) - 录制新序列
|
|
544
|
+
- [`record_get`](#record_get) - 查看序列内容
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## 完整示例:录制与回放流程
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
// 完整的录制回放示例
|
|
552
|
+
async function recordAndReplayDemo() {
|
|
553
|
+
try {
|
|
554
|
+
// 1. 开始录制
|
|
555
|
+
const recordSession = await record_start({
|
|
556
|
+
name: "商品搜索测试",
|
|
557
|
+
description: "测试搜索功能并查看搜索结果"
|
|
558
|
+
})
|
|
559
|
+
console.log(`✅ 录制已开始: ${recordSession.sequenceId}`)
|
|
560
|
+
|
|
561
|
+
// 2. 执行自动化操作(这些会被自动记录)
|
|
562
|
+
await miniprogram_navigate({
|
|
563
|
+
method: "navigateTo",
|
|
564
|
+
url: "/pages/search/search"
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
await element_input({
|
|
568
|
+
selector: "#search-input",
|
|
569
|
+
value: "手机"
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
await element_tap({
|
|
573
|
+
selector: ".search-btn"
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
await page_waitFor({
|
|
577
|
+
selector: ".product-list",
|
|
578
|
+
timeout: 5000
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// 3. 停止录制
|
|
582
|
+
const stopResult = await record_stop()
|
|
583
|
+
console.log(`✅ 录制已停止: ${stopResult.actionCount} 个动作`)
|
|
584
|
+
|
|
585
|
+
const sequenceId = stopResult.sequenceId
|
|
586
|
+
|
|
587
|
+
// 4. 查看录制内容
|
|
588
|
+
const { sequence } = await record_get({ sequenceId })
|
|
589
|
+
console.log("\n录制的动作:")
|
|
590
|
+
sequence.actions.forEach((action, i) => {
|
|
591
|
+
console.log(` ${i + 1}. ${action.toolName}`)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
// 5. 回放序列
|
|
595
|
+
console.log("\n开始回放...")
|
|
596
|
+
const replayResult = await record_replay({
|
|
597
|
+
sequenceId,
|
|
598
|
+
continueOnError: true
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
// 6. 显示回放结果
|
|
602
|
+
if (replayResult.success) {
|
|
603
|
+
console.log("✅ 回放成功!")
|
|
604
|
+
} else {
|
|
605
|
+
console.log(`⚠️ 回放完成: ${replayResult.successCount}/${replayResult.totalActions} 成功`)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 7. 列出所有序列
|
|
609
|
+
const { sequences } = await record_list()
|
|
610
|
+
console.log(`\n当前共有 ${sequences.length} 个序列:`)
|
|
611
|
+
sequences.forEach(seq => {
|
|
612
|
+
console.log(` - ${seq.name} (${seq.actionCount} 动作)`)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.error("❌ 操作失败:", error.message)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
recordAndReplayDemo()
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## 高级用例
|
|
626
|
+
|
|
627
|
+
### 用例 1: 自动化回归测试套件
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
// 定义测试套件
|
|
631
|
+
const testSuites = [
|
|
632
|
+
{ id: "seq_login", name: "登录测试" },
|
|
633
|
+
{ id: "seq_search", name: "搜索测试" },
|
|
634
|
+
{ id: "seq_cart", name: "购物车测试" }
|
|
635
|
+
]
|
|
636
|
+
|
|
637
|
+
// 批量执行测试
|
|
638
|
+
async function runTestSuite() {
|
|
639
|
+
const results = []
|
|
640
|
+
|
|
641
|
+
for (const suite of testSuites) {
|
|
642
|
+
console.log(`\n运行测试: ${suite.name}`)
|
|
643
|
+
|
|
644
|
+
const result = await record_replay({
|
|
645
|
+
sequenceId: suite.id,
|
|
646
|
+
continueOnError: true
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
results.push({
|
|
650
|
+
name: suite.name,
|
|
651
|
+
passed: result.success,
|
|
652
|
+
successRate: (result.successCount / result.totalActions) * 100
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 生成测试报告
|
|
657
|
+
console.log("\n=== 测试报告 ===")
|
|
658
|
+
results.forEach(r => {
|
|
659
|
+
const status = r.passed ? "✅" : "❌"
|
|
660
|
+
console.log(`${status} ${r.name}: ${r.successRate.toFixed(2)}% 通过`)
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
const overallPass = results.every(r => r.passed)
|
|
664
|
+
console.log(`\n总体结果: ${overallPass ? "✅ 通过" : "❌ 失败"}`)
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### 用例 2: 序列对比分析
|
|
669
|
+
|
|
670
|
+
```javascript
|
|
671
|
+
// 对比两次执行结果
|
|
672
|
+
async function compareExecutions(sequenceId) {
|
|
673
|
+
// 第一次执行
|
|
674
|
+
const run1 = await record_replay({
|
|
675
|
+
sequenceId,
|
|
676
|
+
continueOnError: true
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
// 等待一段时间
|
|
680
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
681
|
+
|
|
682
|
+
// 第二次执行
|
|
683
|
+
const run2 = await record_replay({
|
|
684
|
+
sequenceId,
|
|
685
|
+
continueOnError: true
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
// 对比结果
|
|
689
|
+
console.log("执行对比:")
|
|
690
|
+
console.log(`Run 1: ${run1.successCount}/${run1.totalActions} 成功`)
|
|
691
|
+
console.log(`Run 2: ${run2.successCount}/${run2.totalActions} 成功`)
|
|
692
|
+
|
|
693
|
+
// 找出结果不一致的动作
|
|
694
|
+
const differences = []
|
|
695
|
+
run1.results.forEach((r1, index) => {
|
|
696
|
+
const r2 = run2.results[index]
|
|
697
|
+
if (r1.success !== r2.success) {
|
|
698
|
+
differences.push({
|
|
699
|
+
index,
|
|
700
|
+
toolName: r1.toolName,
|
|
701
|
+
run1: r1.success,
|
|
702
|
+
run2: r2.success
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
if (differences.length > 0) {
|
|
708
|
+
console.log("\n不稳定的动作:")
|
|
709
|
+
differences.forEach(d => {
|
|
710
|
+
console.log(` ${d.index + 1}. ${d.toolName}`)
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### 用例 3: 序列管理与清理
|
|
717
|
+
|
|
718
|
+
```javascript
|
|
719
|
+
// 序列管理工具
|
|
720
|
+
async function manageSequences() {
|
|
721
|
+
const { sequences } = await record_list()
|
|
722
|
+
|
|
723
|
+
// 按创建时间排序
|
|
724
|
+
sequences.sort((a, b) =>
|
|
725
|
+
new Date(b.createdAt) - new Date(a.createdAt)
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
// 显示统计信息
|
|
729
|
+
const totalActions = sequences.reduce((sum, seq) => sum + seq.actionCount, 0)
|
|
730
|
+
console.log(`总序列数: ${sequences.length}`)
|
|
731
|
+
console.log(`总动作数: ${totalActions}`)
|
|
732
|
+
console.log(`平均动作数: ${(totalActions / sequences.length).toFixed(2)}`)
|
|
733
|
+
|
|
734
|
+
// 清理超过30天的序列
|
|
735
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000
|
|
736
|
+
let deletedCount = 0
|
|
737
|
+
|
|
738
|
+
for (const seq of sequences) {
|
|
739
|
+
if (new Date(seq.createdAt).getTime() < thirtyDaysAgo) {
|
|
740
|
+
await record_delete({ sequenceId: seq.id })
|
|
741
|
+
console.log(`已删除旧序列: ${seq.name}`)
|
|
742
|
+
deletedCount++
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
console.log(`\n清理完成: 删除了 ${deletedCount} 个序列`)
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## 序列文件格式
|
|
753
|
+
|
|
754
|
+
录制的序列保存为 JSON 文件,格式如下:
|
|
755
|
+
|
|
756
|
+
```json
|
|
757
|
+
{
|
|
758
|
+
"id": "seq_1738425600000_abc123",
|
|
759
|
+
"name": "用户登录流程",
|
|
760
|
+
"description": "测试用户登录并跳转到个人中心",
|
|
761
|
+
"createdAt": "2025-02-01T10:00:00.000Z",
|
|
762
|
+
"actions": [
|
|
763
|
+
{
|
|
764
|
+
"timestamp": "2025-02-01T10:00:01.234Z",
|
|
765
|
+
"toolName": "miniprogram_navigate",
|
|
766
|
+
"args": {
|
|
767
|
+
"method": "navigateTo",
|
|
768
|
+
"url": "/pages/login/login"
|
|
769
|
+
},
|
|
770
|
+
"duration": 150,
|
|
771
|
+
"success": true
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
"timestamp": "2025-02-01T10:00:02.456Z",
|
|
775
|
+
"toolName": "element_input",
|
|
776
|
+
"args": {
|
|
777
|
+
"selector": "#username",
|
|
778
|
+
"value": "testuser"
|
|
779
|
+
},
|
|
780
|
+
"duration": 80,
|
|
781
|
+
"success": true
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
"timestamp": "2025-02-01T10:00:03.789Z",
|
|
785
|
+
"toolName": "element_tap",
|
|
786
|
+
"args": {
|
|
787
|
+
"selector": ".login-btn"
|
|
788
|
+
},
|
|
789
|
+
"duration": 120,
|
|
790
|
+
"success": false,
|
|
791
|
+
"error": "Element not found: .login-btn"
|
|
792
|
+
}
|
|
793
|
+
]
|
|
794
|
+
}
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### 字段说明
|
|
798
|
+
|
|
799
|
+
- **id**: 序列唯一标识符(格式: `seq_{timestamp}_{random}`)
|
|
800
|
+
- **name**: 序列名称
|
|
801
|
+
- **description**: 序列描述(可选)
|
|
802
|
+
- **createdAt**: 创建时间(ISO 8601 格式)
|
|
803
|
+
- **actions**: 动作数组
|
|
804
|
+
- **timestamp**: 动作执行时间
|
|
805
|
+
- **toolName**: 工具名称
|
|
806
|
+
- **args**: 工具参数(原始参数对象)
|
|
807
|
+
- **duration**: 执行耗时(毫秒,可选)
|
|
808
|
+
- **success**: 执行结果(true/false)
|
|
809
|
+
- **error**: 错误消息(仅失败时有)
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## 故障排除
|
|
814
|
+
|
|
815
|
+
### 问题 1: 录制未记录动作
|
|
816
|
+
|
|
817
|
+
**症状**: `stop` 时显示 0 个动作
|
|
818
|
+
|
|
819
|
+
**解决方案**:
|
|
820
|
+
1. 确认录制已成功启动(`start` 返回 success: true)
|
|
821
|
+
2. 检查执行的工具是否在录制工具包装器中
|
|
822
|
+
3. 确认没有在录制期间调用 `stop({ save: false })`
|
|
823
|
+
|
|
824
|
+
### 问题 2: 回放失败率高
|
|
825
|
+
|
|
826
|
+
**症状**: 回放时大量动作失败
|
|
827
|
+
|
|
828
|
+
**解决方案**:
|
|
829
|
+
1. 检查环境状态是否与录制时一致(页面路径、数据等)
|
|
830
|
+
2. 确认动态数据(如 refId)是否已失效
|
|
831
|
+
3. 使用 `page_query` 重新获取元素引用,而非直接使用录制的 refId
|
|
832
|
+
4. 考虑录制时使用 selector 而非 refId
|
|
833
|
+
|
|
834
|
+
### 问题 3: 序列文件损坏
|
|
835
|
+
|
|
836
|
+
**症状**: `get` 或 `replay` 时报 JSON 解析错误
|
|
837
|
+
|
|
838
|
+
**解决方案**:
|
|
839
|
+
1. 检查序列文件是否完整(手动打开查看)
|
|
840
|
+
2. 确认录制时 `stop` 正常完成
|
|
841
|
+
3. 删除损坏的序列文件并重新录制
|
|
842
|
+
|
|
843
|
+
### 问题 4: 回放速度过快
|
|
844
|
+
|
|
845
|
+
**症状**: 回放时动作执行过快导致失败
|
|
846
|
+
|
|
847
|
+
**解决方案**:
|
|
848
|
+
1. 在关键步骤添加 `page_waitFor` 等待元素加载
|
|
849
|
+
2. 使用 `miniprogram_wait` 添加延迟
|
|
850
|
+
3. 录制时确保每步操作都有适当的等待
|
|
851
|
+
|
|
852
|
+
---
|
|
853
|
+
|
|
854
|
+
## 技术细节
|
|
855
|
+
|
|
856
|
+
### 录制机制
|
|
857
|
+
|
|
858
|
+
- **拦截层**: 通过工具包装器拦截所有工具调用
|
|
859
|
+
- **自动记录**: 录制期间自动调用 `recordAction()` 记录每个动作
|
|
860
|
+
- **元数据**: 记录时间戳、参数、结果、耗时等完整信息
|
|
861
|
+
|
|
862
|
+
### 存储位置
|
|
863
|
+
|
|
864
|
+
- **目录**: `.mcp-artifacts/{sessionId}/sequences/`
|
|
865
|
+
- **文件名**: `{sequenceId}.json`
|
|
866
|
+
- **权限**: 自动创建目录(recursive: true)
|
|
867
|
+
|
|
868
|
+
### 回放策略
|
|
869
|
+
|
|
870
|
+
- **顺序执行**: 严格按录制顺序执行
|
|
871
|
+
- **参数重用**: 使用录制时的原始参数
|
|
872
|
+
- **错误处理**: 支持遇错停止或继续执行
|
|
873
|
+
- **结果收集**: 记录每个动作的执行结果
|
|
874
|
+
|
|
875
|
+
### 性能考虑
|
|
876
|
+
|
|
877
|
+
- **文件I/O**: 序列保存和读取涉及磁盘操作
|
|
878
|
+
- **内存占用**: 大型序列会占用更多内存
|
|
879
|
+
- **回放速度**: 通常比原录制更快(无人工等待)
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## 最佳实践
|
|
884
|
+
|
|
885
|
+
### 1. 命名规范
|
|
886
|
+
|
|
887
|
+
```javascript
|
|
888
|
+
// ✅ 推荐:清晰描述测试场景
|
|
889
|
+
await record_start({
|
|
890
|
+
name: "用户登录-成功路径",
|
|
891
|
+
description: "使用正确的用户名密码登录并验证跳转"
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
// ❌ 避免:模糊的名称
|
|
895
|
+
await record_start({
|
|
896
|
+
name: "test1"
|
|
897
|
+
})
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
### 2. 录制粒度
|
|
901
|
+
|
|
902
|
+
```javascript
|
|
903
|
+
// ✅ 推荐:单一功能点
|
|
904
|
+
await record_start({ name: "商品搜索" })
|
|
905
|
+
// 只录制搜索相关操作
|
|
906
|
+
await record_stop()
|
|
907
|
+
|
|
908
|
+
await record_start({ name: "商品详情" })
|
|
909
|
+
// 只录制详情页操作
|
|
910
|
+
await record_stop()
|
|
911
|
+
|
|
912
|
+
// ❌ 避免:混合多个无关功能
|
|
913
|
+
await record_start({ name: "整个流程" })
|
|
914
|
+
// 搜索、详情、购物车、结算...
|
|
915
|
+
await record_stop()
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
### 3. 使用 selector 而非 refId
|
|
919
|
+
|
|
920
|
+
```javascript
|
|
921
|
+
// ✅ 推荐:使用 selector(回放更稳定)
|
|
922
|
+
await element_tap({ selector: ".login-btn" })
|
|
923
|
+
|
|
924
|
+
// ⚠️ 注意:refId 在回放时可能失效
|
|
925
|
+
await element_tap({ refId: "elem_123" })
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### 4. 添加等待步骤
|
|
929
|
+
|
|
930
|
+
```javascript
|
|
931
|
+
// ✅ 推荐:录制时包含等待
|
|
932
|
+
await element_tap({ selector: ".submit-btn" })
|
|
933
|
+
await page_waitFor({ selector: ".success-message", timeout: 5000 })
|
|
934
|
+
|
|
935
|
+
// ❌ 避免:无等待(回放可能失败)
|
|
936
|
+
await element_tap({ selector: ".submit-btn" })
|
|
937
|
+
await element_tap({ selector: ".next-btn" }) // 可能执行过快
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### 5. 定期清理
|
|
941
|
+
|
|
942
|
+
```javascript
|
|
943
|
+
// 定期清理旧序列(避免占用过多空间)
|
|
944
|
+
async function cleanup() {
|
|
945
|
+
const { sequences } = await record_list()
|
|
946
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
|
|
947
|
+
|
|
948
|
+
for (const seq of sequences) {
|
|
949
|
+
if (new Date(seq.createdAt).getTime() < sevenDaysAgo) {
|
|
950
|
+
await record_delete({ sequenceId: seq.id })
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
**相关文档**:
|
|
959
|
+
- [Automator API](./automator.md) - 启动和连接
|
|
960
|
+
- [Element API](./element.md) - 元素操作
|
|
961
|
+
- [测试指南](../testing-guide.md) - 测试最佳实践
|
|
962
|
+
|
|
963
|
+
**最后更新**: 2025-10-02
|