@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.
Files changed (160) hide show
  1. package/README.md +469 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +144 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config/defaults.d.ts +73 -0
  7. package/dist/config/defaults.d.ts.map +1 -0
  8. package/dist/config/defaults.js +118 -0
  9. package/dist/config/defaults.js.map +1 -0
  10. package/dist/config/loader.d.ts +50 -0
  11. package/dist/config/loader.d.ts.map +1 -0
  12. package/dist/config/loader.js +189 -0
  13. package/dist/config/loader.js.map +1 -0
  14. package/dist/core/element-ref.d.ts +44 -0
  15. package/dist/core/element-ref.d.ts.map +1 -0
  16. package/dist/core/element-ref.js +213 -0
  17. package/dist/core/element-ref.js.map +1 -0
  18. package/dist/core/logger.d.ts +55 -0
  19. package/dist/core/logger.d.ts.map +1 -0
  20. package/dist/core/logger.js +378 -0
  21. package/dist/core/logger.js.map +1 -0
  22. package/dist/core/output.d.ts +21 -0
  23. package/dist/core/output.d.ts.map +1 -0
  24. package/dist/core/output.js +56 -0
  25. package/dist/core/output.js.map +1 -0
  26. package/dist/core/report-generator.d.ts +24 -0
  27. package/dist/core/report-generator.d.ts.map +1 -0
  28. package/dist/core/report-generator.js +212 -0
  29. package/dist/core/report-generator.js.map +1 -0
  30. package/dist/core/session.d.ts +83 -0
  31. package/dist/core/session.d.ts.map +1 -0
  32. package/dist/core/session.js +306 -0
  33. package/dist/core/session.js.map +1 -0
  34. package/dist/core/timeout.d.ts +49 -0
  35. package/dist/core/timeout.d.ts.map +1 -0
  36. package/dist/core/timeout.js +67 -0
  37. package/dist/core/timeout.js.map +1 -0
  38. package/dist/core/tool-logger.d.ts +83 -0
  39. package/dist/core/tool-logger.d.ts.map +1 -0
  40. package/dist/core/tool-logger.js +453 -0
  41. package/dist/core/tool-logger.js.map +1 -0
  42. package/dist/core/validation.d.ts +39 -0
  43. package/dist/core/validation.d.ts.map +1 -0
  44. package/dist/core/validation.js +93 -0
  45. package/dist/core/validation.js.map +1 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +6 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/server.d.ts +7 -0
  51. package/dist/server.d.ts.map +1 -0
  52. package/dist/server.js +85 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/tools/assert.d.ts +108 -0
  55. package/dist/tools/assert.d.ts.map +1 -0
  56. package/dist/tools/assert.js +291 -0
  57. package/dist/tools/assert.js.map +1 -0
  58. package/dist/tools/automator.d.ts +45 -0
  59. package/dist/tools/automator.d.ts.map +1 -0
  60. package/dist/tools/automator.js +186 -0
  61. package/dist/tools/automator.js.map +1 -0
  62. package/dist/tools/element.d.ts +253 -0
  63. package/dist/tools/element.d.ts.map +1 -0
  64. package/dist/tools/element.js +615 -0
  65. package/dist/tools/element.js.map +1 -0
  66. package/dist/tools/index.d.ts +97 -0
  67. package/dist/tools/index.d.ts.map +1 -0
  68. package/dist/tools/index.js +1565 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/tools/miniprogram.d.ts +79 -0
  71. package/dist/tools/miniprogram.d.ts.map +1 -0
  72. package/dist/tools/miniprogram.js +245 -0
  73. package/dist/tools/miniprogram.js.map +1 -0
  74. package/dist/tools/network.d.ts +65 -0
  75. package/dist/tools/network.d.ts.map +1 -0
  76. package/dist/tools/network.js +205 -0
  77. package/dist/tools/network.js.map +1 -0
  78. package/dist/tools/page.d.ts +108 -0
  79. package/dist/tools/page.d.ts.map +1 -0
  80. package/dist/tools/page.js +307 -0
  81. package/dist/tools/page.js.map +1 -0
  82. package/dist/tools/record.d.ts +86 -0
  83. package/dist/tools/record.d.ts.map +1 -0
  84. package/dist/tools/record.js +316 -0
  85. package/dist/tools/record.js.map +1 -0
  86. package/dist/tools/snapshot.d.ts +82 -0
  87. package/dist/tools/snapshot.d.ts.map +1 -0
  88. package/dist/tools/snapshot.js +258 -0
  89. package/dist/tools/snapshot.js.map +1 -0
  90. package/dist/types.d.ts +240 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +5 -0
  93. package/dist/types.js.map +1 -0
  94. package/docs/SIMPLE_USAGE.md +210 -0
  95. package/docs/api/README.md +244 -0
  96. package/docs/api/assert.md +1015 -0
  97. package/docs/api/automator.md +345 -0
  98. package/docs/api/element.md +1454 -0
  99. package/docs/api/miniprogram.md +558 -0
  100. package/docs/api/network.md +883 -0
  101. package/docs/api/page.md +909 -0
  102. package/docs/api/record.md +963 -0
  103. package/docs/api/snapshot.md +792 -0
  104. package/docs/architecture.E-Docs.md +1359 -0
  105. package/docs/architecture.F1.md +720 -0
  106. package/docs/architecture.F2.md +871 -0
  107. package/docs/architecture.F3.md +905 -0
  108. package/docs/architecture.md +90 -0
  109. package/docs/charter.A1.align.yaml +170 -0
  110. package/docs/charter.A2.align.yaml +199 -0
  111. package/docs/charter.A3.align.yaml +242 -0
  112. package/docs/charter.A4.align.yaml +227 -0
  113. package/docs/charter.B1.align.yaml +179 -0
  114. package/docs/charter.B2.align.yaml +200 -0
  115. package/docs/charter.B3.align.yaml +200 -0
  116. package/docs/charter.B4.align.yaml +188 -0
  117. package/docs/charter.C1.align.yaml +190 -0
  118. package/docs/charter.C2.align.yaml +202 -0
  119. package/docs/charter.C3.align.yaml +211 -0
  120. package/docs/charter.C4.align.yaml +263 -0
  121. package/docs/charter.C5.align.yaml +220 -0
  122. package/docs/charter.D1.align.yaml +190 -0
  123. package/docs/charter.D2.align.yaml +234 -0
  124. package/docs/charter.D3.align.yaml +206 -0
  125. package/docs/charter.E-Docs.align.yaml +294 -0
  126. package/docs/charter.F1.align.yaml +193 -0
  127. package/docs/charter.F2.align.yaml +248 -0
  128. package/docs/charter.F3.align.yaml +287 -0
  129. package/docs/charter.G.align.yaml +174 -0
  130. package/docs/charter.align.yaml +111 -0
  131. package/docs/examples/session-report-usage.md +449 -0
  132. package/docs/maintenance.md +682 -0
  133. package/docs/playwright-mcp/350/260/203/347/240/224.md +53 -0
  134. package/docs/setup-guide.md +775 -0
  135. package/docs/tasks.A1.atomize.md +296 -0
  136. package/docs/tasks.A2.atomize.md +408 -0
  137. package/docs/tasks.A3.atomize.md +564 -0
  138. package/docs/tasks.A4.atomize.md +496 -0
  139. package/docs/tasks.B1.atomize.md +352 -0
  140. package/docs/tasks.B2.atomize.md +561 -0
  141. package/docs/tasks.B3.atomize.md +508 -0
  142. package/docs/tasks.B4.atomize.md +504 -0
  143. package/docs/tasks.C1.atomize.md +540 -0
  144. package/docs/tasks.C2.atomize.md +665 -0
  145. package/docs/tasks.C3.atomize.md +745 -0
  146. package/docs/tasks.C4.atomize.md +908 -0
  147. package/docs/tasks.C5.atomize.md +755 -0
  148. package/docs/tasks.D1.atomize.md +547 -0
  149. package/docs/tasks.D2.atomize.md +619 -0
  150. package/docs/tasks.D3.atomize.md +790 -0
  151. package/docs/tasks.E-Docs.atomize.md +1204 -0
  152. package/docs/tasks.atomize.md +189 -0
  153. package/docs/troubleshooting.md +855 -0
  154. package/docs//345/256/214/346/225/264/345/256/236/347/216/260/346/226/271/346/241/210.md +155 -0
  155. package/docs//345/274/200/345/217/221/344/273/273/345/212/241/350/256/241/345/210/222.md +110 -0
  156. 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
  157. 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
  158. package/docs//346/216/245/345/217/243/346/226/271/346/241/210.md +565 -0
  159. package/docs//347/254/254/344/270/200/347/211/210/346/234/254/346/226/271/346/241/210.md +380 -0
  160. package/package.json +87 -0
@@ -0,0 +1,1015 @@
1
+ # Assert API
2
+
3
+ > Assert 工具提供自动化测试断言功能,用于验证元素存在性、文本内容、属性值和页面数据,支持完整的测试脚本编写。
4
+
5
+ ## 工具列表
6
+
7
+ | 工具名称 | 描述 | 主要用途 |
8
+ |---------|------|----------|
9
+ | `assert_exists` | 验证元素存在 | 检查页面是否包含特定元素 |
10
+ | `assert_not_exists` | 验证元素不存在 | 检查元素是否已移除 |
11
+ | `assert_text` | 精确文本匹配 | 验证元素文本是否完全一致 |
12
+ | `assert_text_contains` | 文本包含检查 | 验证元素文本是否包含子串 |
13
+ | `assert_value` | 输入值验证 | 验证表单输入框的值 |
14
+ | `assert_attribute` | 属性值验证 | 验证元素 HTML 属性 |
15
+ | `assert_property` | 属性对象验证 | 验证元素 DOM 属性 |
16
+ | `assert_data` | 页面数据验证 | 验证页面 data 状态 |
17
+ | `assert_visible` | 可见性验证 | 验证元素是否可见(非零尺寸) |
18
+
19
+ ---
20
+
21
+ ## assert_exists
22
+
23
+ 验证指定选择器的元素存在于页面中。
24
+
25
+ ### 参数
26
+
27
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
28
+ |--------|------|------|--------|------|
29
+ | `selector` | string | ✅ | - | CSS 选择器 |
30
+ | `pagePath` | string | ⭐ | currentPage | 页面路径(默认当前页面) |
31
+
32
+ ### 返回值
33
+
34
+ ```typescript
35
+ {
36
+ success: true,
37
+ message: "Element exists: .submit-btn"
38
+ }
39
+ ```
40
+
41
+ ### 错误处理
42
+
43
+ - **元素不存在**: `Error: Assertion failed: Element not found with selector: {selector}`
44
+
45
+ ### 使用示例
46
+
47
+ ```javascript
48
+ // 示例 1: 验证按钮存在
49
+ await assert_exists({
50
+ selector: ".submit-btn"
51
+ })
52
+ console.log("✅ 提交按钮存在")
53
+
54
+ // 示例 2: 验证成功提示出现
55
+ await element_tap({ refId: btn.refId })
56
+ await page_wait_for({
57
+ selector: ".success-toast",
58
+ timeout: 2000
59
+ })
60
+ await assert_exists({
61
+ selector: ".success-toast"
62
+ })
63
+ console.log("✅ 成功提示已显示")
64
+
65
+ // 示例 3: 验证特定页面的元素
66
+ await assert_exists({
67
+ selector: ".user-profile",
68
+ pagePath: "/pages/user/index"
69
+ })
70
+
71
+ // 示例 4: 验证列表项数量
72
+ await assert_exists({
73
+ selector: ".product-item:nth-child(5)"
74
+ })
75
+ console.log("✅ 至少有 5 个商品")
76
+
77
+ // 示例 5: 测试流程中的断言
78
+ try {
79
+ await element_input({ refId: input.refId, value: "test" })
80
+ await element_tap({ refId: submitBtn.refId })
81
+ await assert_exists({ selector: ".success-message" })
82
+ console.log("✅ 表单提交成功")
83
+ } catch (error) {
84
+ console.error("❌ 测试失败:", error.message)
85
+ }
86
+ ```
87
+
88
+ ### 注意事项
89
+
90
+ - 💡 **仅验证存在**: 不关心元素是否可见(display: none 的元素也算存在)
91
+ - 💡 **立即执行**: 不等待元素出现,如需等待请先使用 `page_wait_for`
92
+ - ⚠️ **失败即抛出**: 断言失败会抛出异常,中断后续代码执行
93
+
94
+ ### 相关工具
95
+
96
+ - [`assert_not_exists`](#assert_not_exists) - 验证元素不存在
97
+ - [`assert_visible`](#assert_visible) - 验证元素可见
98
+ - [`page_wait_for`](./page.md#page_wait_for) - 等待元素出现
99
+
100
+ ---
101
+
102
+ ## assert_not_exists
103
+
104
+ 验证指定选择器的元素不存在于页面中(已移除或从未存在)。
105
+
106
+ ### 参数
107
+
108
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
109
+ |--------|------|------|--------|------|
110
+ | `selector` | string | ✅ | - | CSS 选择器 |
111
+ | `pagePath` | string | ⭐ | currentPage | 页面路径 |
112
+
113
+ ### 返回值
114
+
115
+ ```typescript
116
+ {
117
+ success: true,
118
+ message: "Element does not exist: .loading-spinner"
119
+ }
120
+ ```
121
+
122
+ ### 错误处理
123
+
124
+ - **元素存在**: `Error: Assertion failed: Element should not exist but found with selector: {selector}`
125
+
126
+ ### 使用示例
127
+
128
+ ```javascript
129
+ // 示例 1: 验证加载动画已消失
130
+ await element_tap({ refId: loadBtn.refId })
131
+ await page_wait_for({ timeout: 2000 })
132
+ await assert_not_exists({
133
+ selector: ".loading-spinner"
134
+ })
135
+ console.log("✅ 加载已完成")
136
+
137
+ // 示例 2: 验证删除操作
138
+ const beforeCount = await page_query_all({
139
+ selector: ".item"
140
+ })
141
+ await element_tap({ refId: deleteBtn.refId })
142
+ await page_wait_for({ timeout: 500 })
143
+ await assert_not_exists({
144
+ selector: ".item:nth-child(5)"
145
+ })
146
+ console.log("✅ 项目已删除")
147
+
148
+ // 示例 3: 验证错误提示消失
149
+ await element_tap({ refId: closeBtn.refId })
150
+ await assert_not_exists({
151
+ selector: ".error-toast"
152
+ })
153
+
154
+ // 示例 4: 验证模态框关闭
155
+ await element_tap({ refId: modalClose.refId })
156
+ await page_wait_for({ timeout: 300 })
157
+ await assert_not_exists({
158
+ selector: ".modal.visible"
159
+ })
160
+ ```
161
+
162
+ ### 注意事项
163
+
164
+ - 💡 **通过条件**: 元素从未存在或已被移除
165
+ - 💡 **display: none**: 设置为 display: none 的元素算"不存在"(取决于选择器)
166
+ - ⚠️ **等待时机**: 如果元素需要时间消失,先使用 `page_wait_for` 或延时
167
+
168
+ ---
169
+
170
+ ## assert_text
171
+
172
+ 验证元素的文本内容与预期值完全一致。
173
+
174
+ ### 参数
175
+
176
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
177
+ |--------|------|------|--------|------|
178
+ | `refId` | string | ✅ | - | 元素引用 ID |
179
+ | `expected` | string | ✅ | - | 预期文本值 |
180
+
181
+ ### 返回值
182
+
183
+ ```typescript
184
+ {
185
+ success: true,
186
+ message: "Text matches: \"Hello World\"",
187
+ actual: "Hello World"
188
+ }
189
+ ```
190
+
191
+ ### 错误处理
192
+
193
+ - **文本不匹配**: `Error: Assertion failed: Text mismatch. Expected: "{expected}", Actual: "{actual}"`
194
+
195
+ ### 使用示例
196
+
197
+ ```javascript
198
+ // 示例 1: 验证标题文本
199
+ const title = await page_query({
200
+ selector: ".page-title",
201
+ save: true
202
+ })
203
+ await assert_text({
204
+ refId: title.refId,
205
+ expected: "商品详情"
206
+ })
207
+ console.log("✅ 标题正确")
208
+
209
+ // 示例 2: 验证计数器
210
+ const counter = await page_query({
211
+ selector: ".count",
212
+ save: true
213
+ })
214
+ await element_tap({ refId: plusBtn.refId })
215
+ await assert_text({
216
+ refId: counter.refId,
217
+ expected: "1"
218
+ })
219
+
220
+ // 示例 3: 验证表单反馈
221
+ await element_input({ refId: input.refId, value: "test@example.com" })
222
+ await element_tap({ refId: submitBtn.refId })
223
+ const feedback = await page_query({
224
+ selector: ".feedback-message",
225
+ save: true
226
+ })
227
+ await assert_text({
228
+ refId: feedback.refId,
229
+ expected: "邮箱格式正确"
230
+ })
231
+
232
+ // 示例 4: 错误处理
233
+ try {
234
+ await assert_text({
235
+ refId: element.refId,
236
+ expected: "预期文本"
237
+ })
238
+ } catch (error) {
239
+ console.error("文本不匹配:", error.message)
240
+ // 错误消息包含期望值和实际值
241
+ }
242
+ ```
243
+
244
+ ### 注意事项
245
+
246
+ - ⚠️ **完全匹配**: 必须完全一致,包括大小写、空格和换行
247
+ - 💡 **部分匹配**: 如需部分匹配,使用 `assert_text_contains`
248
+ - 💡 **空白字符**: 注意前后空白字符,可能需要 trim 处理
249
+
250
+ ---
251
+
252
+ ## assert_text_contains
253
+
254
+ 验证元素文本包含指定的子串。
255
+
256
+ ### 参数
257
+
258
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
259
+ |--------|------|------|--------|------|
260
+ | `refId` | string | ✅ | - | 元素引用 ID |
261
+ | `expected` | string | ✅ | - | 预期包含的子串 |
262
+
263
+ ### 返回值
264
+
265
+ ```typescript
266
+ {
267
+ success: true,
268
+ message: "Text contains: \"iPhone\"",
269
+ actual: "Apple iPhone 15 Pro Max 256GB"
270
+ }
271
+ ```
272
+
273
+ ### 错误处理
274
+
275
+ - **不包含子串**: `Error: Assertion failed: Text does not contain expected substring. Expected to contain: "{expected}", Actual: "{actual}"`
276
+
277
+ ### 使用示例
278
+
279
+ ```javascript
280
+ // 示例 1: 验证商品名称包含关键词
281
+ const productName = await page_query({
282
+ selector: ".product-name",
283
+ save: true
284
+ })
285
+ await assert_text_contains({
286
+ refId: productName.refId,
287
+ expected: "iPhone"
288
+ })
289
+ console.log("✅ 商品名称包含 iPhone")
290
+
291
+ // 示例 2: 验证价格格式
292
+ const price = await page_query({
293
+ selector: ".price",
294
+ save: true
295
+ })
296
+ await assert_text_contains({
297
+ refId: price.refId,
298
+ expected: "¥"
299
+ })
300
+
301
+ // 示例 3: 验证错误提示包含关键信息
302
+ await element_tap({ refId: submitBtn.refId })
303
+ const error = await page_query({
304
+ selector: ".error-message",
305
+ save: true
306
+ })
307
+ await assert_text_contains({
308
+ refId: error.refId,
309
+ expected: "用户名"
310
+ })
311
+
312
+ // 示例 4: 验证列表描述
313
+ const description = await page_query({
314
+ selector: ".item-description",
315
+ save: true
316
+ })
317
+ await assert_text_contains({
318
+ refId: description.refId,
319
+ expected: "免运费"
320
+ })
321
+ ```
322
+
323
+ ### 注意事项
324
+
325
+ - 💡 **子串匹配**: 只要包含子串即可,不要求完全一致
326
+ - 💡 **大小写敏感**: 区分大小写
327
+ - 💡 **灵活性**: 比 `assert_text` 更灵活,适合动态内容
328
+
329
+ ---
330
+
331
+ ## assert_value
332
+
333
+ 验证输入框或表单组件的值与预期一致。
334
+
335
+ ### 参数
336
+
337
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
338
+ |--------|------|------|--------|------|
339
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是表单组件) |
340
+ | `expected` | string | ✅ | - | 预期值 |
341
+
342
+ ### 返回值
343
+
344
+ ```typescript
345
+ {
346
+ success: true,
347
+ message: "Value matches: \"test@example.com\"",
348
+ actual: "test@example.com"
349
+ }
350
+ ```
351
+
352
+ ### 错误处理
353
+
354
+ - **值不匹配**: `Error: Assertion failed: Value mismatch. Expected: "{expected}", Actual: "{actual}"`
355
+
356
+ ### 使用示例
357
+
358
+ ```javascript
359
+ // 示例 1: 验证输入框值
360
+ const input = await page_query({
361
+ selector: "#email",
362
+ save: true
363
+ })
364
+ await element_input({
365
+ refId: input.refId,
366
+ value: "user@example.com"
367
+ })
368
+ await assert_value({
369
+ refId: input.refId,
370
+ expected: "user@example.com"
371
+ })
372
+ console.log("✅ 输入值正确")
373
+
374
+ // 示例 2: 验证表单提交前的数据
375
+ const username = await page_query({
376
+ selector: "#username",
377
+ save: true
378
+ })
379
+ const password = await page_query({
380
+ selector: "#password",
381
+ save: true
382
+ })
383
+
384
+ await element_input({ refId: username.refId, value: "alice" })
385
+ await element_input({ refId: password.refId, value: "secret123" })
386
+
387
+ await assert_value({ refId: username.refId, expected: "alice" })
388
+ await assert_value({ refId: password.refId, expected: "secret123" })
389
+
390
+ await element_tap({ refId: submitBtn.refId })
391
+
392
+ // 示例 3: 验证默认值
393
+ const select = await page_query({
394
+ selector: "picker",
395
+ save: true
396
+ })
397
+ await assert_value({
398
+ refId: select.refId,
399
+ expected: "0" // 默认选中第一项
400
+ })
401
+ ```
402
+
403
+ ### 注意事项
404
+
405
+ - ⚠️ **仅表单组件**: 仅适用于 input, textarea, picker 等表单组件
406
+ - 💡 **输入后验证**: 通常在 `element_input` 后立即验证
407
+ - 💡 **类型转换**: 值始终为字符串类型
408
+
409
+ ---
410
+
411
+ ## assert_attribute
412
+
413
+ 验证元素的 HTML 属性值与预期一致。
414
+
415
+ ### 参数
416
+
417
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
418
+ |--------|------|------|--------|------|
419
+ | `refId` | string | ✅ | - | 元素引用 ID |
420
+ | `name` | string | ✅ | - | 属性名称 |
421
+ | `expected` | string | ✅ | - | 预期属性值 |
422
+
423
+ ### 返回值
424
+
425
+ ```typescript
426
+ {
427
+ success: true,
428
+ message: "Attribute \"class\" matches: \"btn primary\"",
429
+ actual: "btn primary"
430
+ }
431
+ ```
432
+
433
+ ### 错误处理
434
+
435
+ - **属性值不匹配**: `Error: Assertion failed: Attribute "{name}" mismatch. Expected: "{expected}", Actual: "{actual}"`
436
+
437
+ ### 使用示例
438
+
439
+ ```javascript
440
+ // 示例 1: 验证 class 属性
441
+ const button = await page_query({
442
+ selector: ".submit-btn",
443
+ save: true
444
+ })
445
+ await assert_attribute({
446
+ refId: button.refId,
447
+ name: "class",
448
+ expected: "submit-btn primary"
449
+ })
450
+
451
+ // 示例 2: 验证自定义属性
452
+ const item = await page_query({
453
+ selector: ".product-item",
454
+ save: true
455
+ })
456
+ await assert_attribute({
457
+ refId: item.refId,
458
+ name: "data-id",
459
+ expected: "12345"
460
+ })
461
+
462
+ // 示例 3: 验证按钮状态
463
+ const btn = await page_query({
464
+ selector: ".action-btn",
465
+ save: true
466
+ })
467
+ // 验证按钮已禁用
468
+ await assert_attribute({
469
+ refId: btn.refId,
470
+ name: "disabled",
471
+ expected: "true"
472
+ })
473
+
474
+ // 示例 4: 验证链接目标
475
+ const link = await page_query({
476
+ selector: ".external-link",
477
+ save: true
478
+ })
479
+ await assert_attribute({
480
+ refId: link.refId,
481
+ name: "href",
482
+ expected: "https://example.com"
483
+ })
484
+ ```
485
+
486
+ ### 注意事项
487
+
488
+ - 💡 **Attribute vs Property**: 属性(attribute)是 HTML 标签上的,属性(property)是 DOM 对象的
489
+ - 💡 **字符串类型**: 属性值始终为字符串
490
+ - ⚠️ **null 值**: 如果属性不存在,`actual` 为 `null`
491
+
492
+ ---
493
+
494
+ ## assert_property
495
+
496
+ 验证元素的 DOM 属性值与预期一致(支持对象和数组)。
497
+
498
+ ### 参数
499
+
500
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
501
+ |--------|------|------|--------|------|
502
+ | `refId` | string | ✅ | - | 元素引用 ID |
503
+ | `name` | string | ✅ | - | 属性名称 |
504
+ | `expected` | any | ✅ | - | 预期属性值(任意类型) |
505
+
506
+ ### 返回值
507
+
508
+ ```typescript
509
+ {
510
+ success: true,
511
+ message: "Property \"checked\" matches",
512
+ actual: true
513
+ }
514
+ ```
515
+
516
+ ### 错误处理
517
+
518
+ - **属性值不匹配**: `Error: Assertion failed: Property "{name}" mismatch. Expected: {expected}, Actual: {actual}`
519
+
520
+ ### 使用示例
521
+
522
+ ```javascript
523
+ // 示例 1: 验证 checkbox 状态
524
+ const checkbox = await page_query({
525
+ selector: "checkbox",
526
+ save: true
527
+ })
528
+ await element_tap({ refId: checkbox.refId })
529
+ await assert_property({
530
+ refId: checkbox.refId,
531
+ name: "checked",
532
+ expected: true
533
+ })
534
+
535
+ // 示例 2: 验证 dataset 对象
536
+ const item = await page_query({
537
+ selector: ".item",
538
+ save: true
539
+ })
540
+ await assert_property({
541
+ refId: item.refId,
542
+ name: "dataset",
543
+ expected: { id: "123", type: "product" }
544
+ })
545
+
546
+ // 示例 3: 验证数组属性
547
+ const list = await page_query({
548
+ selector: "custom-list",
549
+ save: true
550
+ })
551
+ await assert_property({
552
+ refId: list.refId,
553
+ name: "items",
554
+ expected: ["item1", "item2", "item3"]
555
+ })
556
+
557
+ // 示例 4: 验证数值属性
558
+ const slider = await page_query({
559
+ selector: "slider",
560
+ save: true
561
+ })
562
+ await assert_property({
563
+ refId: slider.refId,
564
+ name: "value",
565
+ expected: 75
566
+ })
567
+ ```
568
+
569
+ ### 注意事项
570
+
571
+ - 💡 **JSON 比较**: 使用 JSON.stringify 进行深度比较
572
+ - 💡 **类型敏感**: 区分 `"123"` 和 `123`
573
+ - 💡 **对象和数组**: 支持复杂数据结构验证
574
+
575
+ ---
576
+
577
+ ## assert_data
578
+
579
+ 验证页面 data 对象或指定路径的数据与预期一致。
580
+
581
+ ### 参数
582
+
583
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
584
+ |--------|------|------|--------|------|
585
+ | `path` | string | ⭐ | - | 数据路径(如 `userInfo.name`),为空验证整个 data |
586
+ | `expected` | any | ✅ | - | 预期数据值 |
587
+ | `pagePath` | string | ⭐ | currentPage | 页面路径 |
588
+
589
+ ### 返回值
590
+
591
+ ```typescript
592
+ {
593
+ success: true,
594
+ message: "Page data at \"userInfo.name\" matches",
595
+ actual: "Alice"
596
+ }
597
+ ```
598
+
599
+ ### 错误处理
600
+
601
+ - **数据不匹配**: `Error: Assertion failed: Page data at path "{path}" mismatch. Expected: {expected}, Actual: {actual}`
602
+
603
+ ### 使用示例
604
+
605
+ ```javascript
606
+ // 示例 1: 验证页面数据字段
607
+ await page_set_data({
608
+ data: { count: 10 }
609
+ })
610
+ await assert_data({
611
+ path: "count",
612
+ expected: 10
613
+ })
614
+ console.log("✅ 计数器数据正确")
615
+
616
+ // 示例 2: 验证嵌套数据
617
+ await assert_data({
618
+ path: "userInfo.name",
619
+ expected: "Alice"
620
+ })
621
+ await assert_data({
622
+ path: "userInfo.age",
623
+ expected: 25
624
+ })
625
+
626
+ // 示例 3: 验证整个对象
627
+ await assert_data({
628
+ path: "userInfo",
629
+ expected: {
630
+ name: "Alice",
631
+ age: 25,
632
+ isVip: true
633
+ }
634
+ })
635
+
636
+ // 示例 4: 验证数组数据
637
+ await assert_data({
638
+ path: "productList",
639
+ expected: [
640
+ { id: 1, name: "商品1" },
641
+ { id: 2, name: "商品2" }
642
+ ]
643
+ })
644
+
645
+ // 示例 5: 验证布尔值
646
+ await assert_data({
647
+ path: "isLoading",
648
+ expected: false
649
+ })
650
+ ```
651
+
652
+ ### 注意事项
653
+
654
+ - 💡 **JSON 比较**: 使用 JSON.stringify 进行深度比较
655
+ - 💡 **路径语法**: 使用点号分隔(`a.b.c`)
656
+ - 💡 **整个 data**: 不提供 `path` 参数验证整个 data 对象
657
+ - ⚠️ **不存在路径**: 路径不存在时 `actual` 为 `undefined`
658
+
659
+ ---
660
+
661
+ ## assert_visible
662
+
663
+ 验证元素是否可见(尺寸非零)。
664
+
665
+ ### 参数
666
+
667
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
668
+ |--------|------|------|--------|------|
669
+ | `refId` | string | ✅ | - | 元素引用 ID |
670
+
671
+ ### 返回值
672
+
673
+ ```typescript
674
+ {
675
+ success: true,
676
+ message: "Element is visible",
677
+ size: {
678
+ width: 100,
679
+ height: 50
680
+ }
681
+ }
682
+ ```
683
+
684
+ ### 错误处理
685
+
686
+ - **元素不可见**: `Error: Assertion failed: Element is not visible. Size: {size}`
687
+
688
+ ### 使用示例
689
+
690
+ ```javascript
691
+ // 示例 1: 验证元素可见
692
+ const button = await page_query({
693
+ selector: ".submit-btn",
694
+ save: true
695
+ })
696
+ await assert_visible({
697
+ refId: button.refId
698
+ })
699
+ console.log("✅ 按钮可见")
700
+
701
+ // 示例 2: 验证动画后的可见性
702
+ await element_tap({ refId: showBtn.refId })
703
+ await page_wait_for({ timeout: 300 }) // 等待动画
704
+ const modal = await page_query({
705
+ selector: ".modal",
706
+ save: true
707
+ })
708
+ await assert_visible({
709
+ refId: modal.refId
710
+ })
711
+
712
+ // 示例 3: 验证隐藏操作
713
+ const tooltip = await page_query({
714
+ selector: ".tooltip",
715
+ save: true
716
+ })
717
+ await element_tap({ refId: closeBtn.refId })
718
+ try {
719
+ await assert_visible({ refId: tooltip.refId })
720
+ console.error("❌ 提示框应该已隐藏")
721
+ } catch {
722
+ console.log("✅ 提示框已隐藏")
723
+ }
724
+
725
+ // 示例 4: 获取可见元素尺寸
726
+ try {
727
+ const result = await assert_visible({ refId: el.refId })
728
+ console.log(`元素尺寸: ${result.size.width}x${result.size.height}`)
729
+ } catch (error) {
730
+ console.log("元素不可见")
731
+ }
732
+ ```
733
+
734
+ ### 注意事项
735
+
736
+ - 💡 **尺寸判断**: 宽度或高度为 0 即认为不可见
737
+ - ⚠️ **display: none**: 元素设置为 display: none 时尺寸为 0
738
+ - ⚠️ **不验证 z-index**: 不检查元素是否被其他元素遮挡
739
+ - 💡 **透明度**: opacity: 0 的元素仍可能有尺寸(取决于布局)
740
+
741
+ ---
742
+
743
+ ## 完整示例:登录表单测试
744
+
745
+ ```javascript
746
+ // 场景:完整的登录表单自动化测试
747
+ async function testLoginForm() {
748
+ try {
749
+ // 1. 启动并导航
750
+ await miniprogram_launch({
751
+ projectPath: "/path/to/project"
752
+ })
753
+ await miniprogram_navigate({
754
+ method: "navigateTo",
755
+ url: "/pages/login/login"
756
+ })
757
+
758
+ // 2. 验证页面元素存在
759
+ await assert_exists({ selector: "#username" })
760
+ await assert_exists({ selector: "#password" })
761
+ await assert_exists({ selector: ".submit-btn" })
762
+ console.log("✅ 页面元素完整")
763
+
764
+ // 3. 查询表单元素
765
+ const username = await page_query({
766
+ selector: "#username",
767
+ save: true
768
+ })
769
+ const password = await page_query({
770
+ selector: "#password",
771
+ save: true
772
+ })
773
+ const submitBtn = await page_query({
774
+ selector: ".submit-btn",
775
+ save: true
776
+ })
777
+
778
+ // 4. 验证初始状态
779
+ await assert_value({
780
+ refId: username.refId,
781
+ expected: ""
782
+ })
783
+ await assert_visible({
784
+ refId: submitBtn.refId
785
+ })
786
+ console.log("✅ 初始状态正确")
787
+
788
+ // 5. 填写表单
789
+ await element_input({
790
+ refId: username.refId,
791
+ value: "testuser"
792
+ })
793
+ await element_input({
794
+ refId: password.refId,
795
+ value: "password123"
796
+ })
797
+
798
+ // 6. 验证输入值
799
+ await assert_value({
800
+ refId: username.refId,
801
+ expected: "testuser"
802
+ })
803
+ await assert_value({
804
+ refId: password.refId,
805
+ expected: "password123"
806
+ })
807
+ console.log("✅ 表单数据填写正确")
808
+
809
+ // 7. 提交表单
810
+ await element_tap({ refId: submitBtn.refId })
811
+
812
+ // 8. 验证加载状态
813
+ await page_wait_for({
814
+ selector: ".loading",
815
+ timeout: 500
816
+ })
817
+ await assert_exists({ selector: ".loading" })
818
+ console.log("✅ 加载中")
819
+
820
+ // 9. 等待并验证成功状态
821
+ await page_wait_for({
822
+ selector: ".success-toast",
823
+ timeout: 3000
824
+ })
825
+ await assert_exists({ selector: ".success-toast" })
826
+ await assert_not_exists({ selector: ".loading" })
827
+
828
+ const toast = await page_query({
829
+ selector: ".success-toast .message",
830
+ save: true
831
+ })
832
+ await assert_text_contains({
833
+ refId: toast.refId,
834
+ expected: "登录成功"
835
+ })
836
+ console.log("✅ 登录成功")
837
+
838
+ // 10. 验证页面跳转
839
+ await page_wait_for({ timeout: 1000 })
840
+ const pageStack = await miniprogram_get_page_stack()
841
+ const currentPage = pageStack.pages[pageStack.pages.length - 1]
842
+ if (currentPage.path === "pages/home/index") {
843
+ console.log("✅ 已跳转到首页")
844
+ }
845
+
846
+ // 11. 验证登录后的数据
847
+ await assert_data({
848
+ path: "userInfo.username",
849
+ expected: "testuser"
850
+ })
851
+ await assert_data({
852
+ path: "isLoggedIn",
853
+ expected: true
854
+ })
855
+ console.log("✅ 用户数据正确")
856
+
857
+ console.log("✅ 所有测试通过")
858
+
859
+ } catch (error) {
860
+ console.error("❌ 测试失败:", error.message)
861
+ await miniprogram_screenshot({
862
+ filename: "test-failure.png"
863
+ })
864
+ throw error
865
+ } finally {
866
+ await miniprogram_close()
867
+ }
868
+ }
869
+
870
+ testLoginForm()
871
+ ```
872
+
873
+ ---
874
+
875
+ ## 故障排除
876
+
877
+ ### 问题 1: 断言失败但元素确实存在
878
+
879
+ **错误**: `Assertion failed: Element not found`
880
+
881
+ **可能原因**:
882
+ 1. 元素尚未渲染完成
883
+ 2. 选择器错误
884
+ 3. 页面路径不正确
885
+
886
+ **解决方案**:
887
+ ```javascript
888
+ // ❌ 错误:元素还在加载中
889
+ await element_tap({ refId: btn.refId })
890
+ await assert_exists({ selector: ".result" }) // 可能失败
891
+
892
+ // ✅ 正确:等待元素出现
893
+ await element_tap({ refId: btn.refId })
894
+ await page_wait_for({
895
+ selector: ".result",
896
+ timeout: 2000
897
+ })
898
+ await assert_exists({ selector: ".result" })
899
+ ```
900
+
901
+ ### 问题 2: 文本断言失败但看起来一样
902
+
903
+ **错误**: `Text mismatch. Expected: "Hello", Actual: "Hello "`
904
+
905
+ **可能原因**:
906
+ 1. 包含不可见空白字符
907
+ 2. 大小写不匹配
908
+ 3. 换行符差异
909
+
910
+ **解决方案**:
911
+ ```javascript
912
+ // 使用 assert_text_contains 降低严格度
913
+ await assert_text_contains({
914
+ refId: el.refId,
915
+ expected: "Hello"
916
+ })
917
+
918
+ // 或者先 trim 处理
919
+ const text = await element_get_text({ refId: el.refId })
920
+ await assert_text({
921
+ refId: el.refId,
922
+ expected: text.text.trim()
923
+ })
924
+ ```
925
+
926
+ ### 问题 3: 数据断言失败但数据正确
927
+
928
+ **错误**: `Page data mismatch`
929
+
930
+ **可能原因**:
931
+ 1. 数据类型不匹配(`"10"` vs `10`)
932
+ 2. 对象属性顺序不同
933
+ 3. 额外的 undefined 字段
934
+
935
+ **解决方案**:
936
+ ```javascript
937
+ // 使用 page_get_data 先查看实际数据
938
+ const actual = await page_get_data({ path: "count" })
939
+ console.log("实际数据:", actual.data, typeof actual.data)
940
+
941
+ // 确保类型匹配
942
+ await assert_data({
943
+ path: "count",
944
+ expected: 10 // number, not "10"
945
+ })
946
+ ```
947
+
948
+ ---
949
+
950
+ ## 最佳实践
951
+
952
+ ### 1. 断言顺序
953
+
954
+ ```javascript
955
+ // ✅ 推荐:先验证存在,再验证内容
956
+ await assert_exists({ selector: ".message" })
957
+ const el = await page_query({ selector: ".message", save: true })
958
+ await assert_text({ refId: el.refId, expected: "Success" })
959
+
960
+ // ❌ 不推荐:直接验证内容(可能因元素不存在而失败)
961
+ const el = await page_query({ selector: ".message", save: true })
962
+ await assert_text({ refId: el.refId, expected: "Success" })
963
+ ```
964
+
965
+ ### 2. 等待 + 断言
966
+
967
+ ```javascript
968
+ // ✅ 推荐:先等待再断言
969
+ await page_wait_for({ selector: ".result", timeout: 2000 })
970
+ await assert_exists({ selector: ".result" })
971
+
972
+ // ❌ 不推荐:仅断言(可能因异步操作失败)
973
+ await assert_exists({ selector: ".result" })
974
+ ```
975
+
976
+ ### 3. 使用合适的断言
977
+
978
+ ```javascript
979
+ // 动态内容:使用 contains
980
+ await assert_text_contains({
981
+ refId: timestamp.refId,
982
+ expected: "2025"
983
+ })
984
+
985
+ // 静态内容:使用精确匹配
986
+ await assert_text({
987
+ refId: title.refId,
988
+ expected: "商品详情"
989
+ })
990
+ ```
991
+
992
+ ### 4. 错误处理
993
+
994
+ ```javascript
995
+ // ✅ 推荐:捕获断言错误并截图
996
+ try {
997
+ await assert_exists({ selector: ".success" })
998
+ } catch (error) {
999
+ await miniprogram_screenshot({
1000
+ filename: "assertion-failure.png"
1001
+ })
1002
+ console.error("断言失败:", error.message)
1003
+ throw error
1004
+ }
1005
+ ```
1006
+
1007
+ ---
1008
+
1009
+ **相关文档**:
1010
+ - [Page API](./page.md) - 页面操作和数据访问
1011
+ - [Element API](./element.md) - 元素交互
1012
+ - [Snapshot API](./snapshot.md) - 状态捕获
1013
+ - [使用示例](../../examples/03-assertion-testing.md) - 断言测试示例
1014
+
1015
+ **最后更新**: 2025-10-02