@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,1454 @@
1
+ # Element API
2
+
3
+ > Element 工具提供元素级别的交互操作、属性获取和组件特定功能,是自动化测试的核心执行层。
4
+
5
+ ## 工具列表
6
+
7
+ ### 基础交互(3 个工具)
8
+
9
+ | 工具名称 | 描述 | 主要用途 |
10
+ |---------|------|----------|
11
+ | `element_tap` | 点击元素 | 按钮点击、链接跳转 |
12
+ | `element_longpress` | 长按元素 | 长按菜单触发 |
13
+ | `element_input` | 输入文本 | 表单填写 |
14
+
15
+ ### 获取信息(7 个工具)
16
+
17
+ | 工具名称 | 描述 | 主要用途 |
18
+ |---------|------|----------|
19
+ | `element_get_text` | 获取文本内容 | 文本验证 |
20
+ | `element_get_value` | 获取输入框值 | 表单数据验证 |
21
+ | `element_get_attribute` | 获取 HTML 属性 | 属性检查 |
22
+ | `element_get_property` | 获取 DOM 属性 | 属性对象访问 |
23
+ | `element_get_style` | 获取样式值 | 样式验证 |
24
+ | `element_get_size` | 获取元素尺寸 | 布局验证 |
25
+ | `element_get_offset` | 获取元素偏移 | 位置验证 |
26
+
27
+ ### 触摸事件(4 个工具)
28
+
29
+ | 工具名称 | 描述 | 主要用途 |
30
+ |---------|------|----------|
31
+ | `element_touchstart` | 触摸开始 | 自定义手势起始 |
32
+ | `element_touchmove` | 触摸移动 | 拖动、滑动操作 |
33
+ | `element_touchend` | 触摸结束 | 手势完成 |
34
+ | `element_trigger` | 触发事件 | 自定义事件模拟 |
35
+
36
+ ### 滚动操作(3 个工具 - ScrollView)
37
+
38
+ | 工具名称 | 描述 | 主要用途 |
39
+ |---------|------|----------|
40
+ | `element_scroll_to` | 滚动到指定位置 | 定位滚动 |
41
+ | `element_scroll_width` | 获取滚动宽度 | 横向滚动范围检查 |
42
+ | `element_scroll_height` | 获取滚动高度 | 纵向滚动范围检查 |
43
+
44
+ ### 组件特定操作(6 个工具)
45
+
46
+ | 工具名称 | 描述 | 组件类型 |
47
+ |---------|------|----------|
48
+ | `element_swipe_to` | 滑动到指定索引 | Swiper |
49
+ | `element_move_to` | 移动到指定位置 | MovableView |
50
+ | `element_slide_to` | 滑动到指定值 | Slider |
51
+ | `element_call_context_method` | 调用上下文方法 | ContextElement |
52
+ | `element_set_data` | 设置自定义组件数据 | CustomElement |
53
+ | `element_call_method` | 调用自定义组件方法 | CustomElement |
54
+
55
+ ---
56
+
57
+ ## 基础交互
58
+
59
+ ### element_tap
60
+
61
+ 点击元素(模拟用户点击操作)。
62
+
63
+ #### 参数
64
+
65
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
66
+ |--------|------|------|--------|------|
67
+ | `refId` | string | ✅ | - | 元素引用 ID(来自 `page_query`) |
68
+
69
+ #### 返回值
70
+
71
+ ```typescript
72
+ {
73
+ success: true,
74
+ message: "Element tapped: elem_abc123"
75
+ }
76
+ ```
77
+
78
+ #### 错误处理
79
+
80
+ - **元素不存在**: `Error: Element not found with refId: {refId}. Use page_query to get element reference first.`
81
+ - **点击失败**: `Error: Tap failed: {reason}`
82
+
83
+ #### 使用示例
84
+
85
+ ```javascript
86
+ // 示例 1: 点击按钮
87
+ const btn = await page_query({
88
+ selector: ".submit-btn",
89
+ save: true
90
+ })
91
+ await element_tap({ refId: btn.refId })
92
+
93
+ // 示例 2: 点击列表项
94
+ const items = await page_query_all({
95
+ selector: ".product-item",
96
+ save: true
97
+ })
98
+ // 点击第 3 个商品
99
+ await element_tap({ refId: items.elements[2].refId })
100
+
101
+ // 示例 3: 点击后验证跳转
102
+ await element_tap({ refId: btn.refId })
103
+ await page_wait_for({
104
+ selector: ".detail-page",
105
+ timeout: 2000
106
+ })
107
+
108
+ // 示例 4: 连续点击
109
+ await element_tap({ refId: plusBtn.refId }) // 数量 +1
110
+ await element_tap({ refId: plusBtn.refId }) // 数量 +2
111
+ const count = await element_get_text({ refId: countDisplay.refId })
112
+ console.log(count.text) // "2"
113
+ ```
114
+
115
+ #### 注意事项
116
+
117
+ - 💡 **必需引用**: 必须先使用 `page_query` 获取元素引用
118
+ - 💡 **等待渲染**: 确保元素已渲染完成,否则使用 `page_wait_for`
119
+ - ⚠️ **事件触发**: 会触发小程序的 tap 事件和相关生命周期
120
+
121
+ #### 相关工具
122
+
123
+ - [`element_longpress`](#element_longpress) - 长按元素
124
+ - [`page_query`](./page.md#page_query) - 查询元素获取 refId
125
+
126
+ ---
127
+
128
+ ### element_longpress
129
+
130
+ 长按元素(模拟用户长按操作)。
131
+
132
+ #### 参数
133
+
134
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
135
+ |--------|------|------|--------|------|
136
+ | `refId` | string | ✅ | - | 元素引用 ID |
137
+
138
+ #### 返回值
139
+
140
+ ```typescript
141
+ {
142
+ success: true,
143
+ message: "Element long pressed: elem_abc123"
144
+ }
145
+ ```
146
+
147
+ #### 使用示例
148
+
149
+ ```javascript
150
+ // 示例 1: 长按触发菜单
151
+ const item = await page_query({
152
+ selector: ".message-item",
153
+ save: true
154
+ })
155
+ await element_longpress({ refId: item.refId })
156
+ await page_wait_for({
157
+ selector: ".context-menu",
158
+ timeout: 1000
159
+ })
160
+
161
+ // 示例 2: 长按删除
162
+ await element_longpress({ refId: item.refId })
163
+ await element_tap({ selector: ".delete-btn" })
164
+ await assert_not_exists({ selector: ".message-item" })
165
+ ```
166
+
167
+ #### 注意事项
168
+
169
+ - 💡 **长按时长**: 默认长按时长由小程序组件决定(通常 350ms)
170
+ - ⚠️ **事件触发**: 会触发 longpress 事件,不会触发 tap 事件
171
+
172
+ ---
173
+
174
+ ### element_input
175
+
176
+ 向输入框或文本域输入文本。
177
+
178
+ #### 参数
179
+
180
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
181
+ |--------|------|------|--------|------|
182
+ | `refId` | string | ✅ | - | 元素引用 ID |
183
+ | `value` | string | ✅ | - | 要输入的文本内容 |
184
+
185
+ #### 返回值
186
+
187
+ ```typescript
188
+ {
189
+ success: true,
190
+ message: "Text input to element: elem_abc123"
191
+ }
192
+ ```
193
+
194
+ #### 错误处理
195
+
196
+ - **元素不可输入**: `Error: Input failed: Element is not an input or textarea`
197
+
198
+ #### 使用示例
199
+
200
+ ```javascript
201
+ // 示例 1: 表单填写
202
+ const username = await page_query({
203
+ selector: "#username",
204
+ save: true
205
+ })
206
+ const password = await page_query({
207
+ selector: "#password",
208
+ save: true
209
+ })
210
+
211
+ await element_input({
212
+ refId: username.refId,
213
+ value: "testuser"
214
+ })
215
+ await element_input({
216
+ refId: password.refId,
217
+ value: "password123"
218
+ })
219
+
220
+ await element_tap({ selector: ".login-btn" })
221
+
222
+ // 示例 2: 搜索操作
223
+ const searchInput = await page_query({
224
+ selector: ".search-input",
225
+ save: true
226
+ })
227
+ await element_input({
228
+ refId: searchInput.refId,
229
+ value: "iPhone 15"
230
+ })
231
+ await element_tap({ selector: ".search-btn" })
232
+
233
+ // 示例 3: 覆盖已有内容
234
+ // 注意:element_input 会覆盖原有内容,不是追加
235
+ await element_input({
236
+ refId: input.refId,
237
+ value: "新内容"
238
+ })
239
+
240
+ // 示例 4: 输入后验证
241
+ await element_input({
242
+ refId: input.refId,
243
+ value: "测试文本"
244
+ })
245
+ const result = await element_get_value({ refId: input.refId })
246
+ console.log(result.value) // "测试文本"
247
+ ```
248
+
249
+ #### 注意事项
250
+
251
+ - ⚠️ **仅限输入组件**: 仅适用于 `input` 和 `textarea` 组件
252
+ - 💡 **覆盖内容**: 会覆盖原有内容,不是追加
253
+ - 💡 **触发事件**: 会触发 input 和 change 事件
254
+
255
+ ---
256
+
257
+ ## 获取信息
258
+
259
+ ### element_get_text
260
+
261
+ 获取元素的文本内容(innerText)。
262
+
263
+ #### 参数
264
+
265
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
266
+ |--------|------|------|--------|------|
267
+ | `refId` | string | ✅ | - | 元素引用 ID |
268
+
269
+ #### 返回值
270
+
271
+ ```typescript
272
+ {
273
+ success: true,
274
+ message: "Element text retrieved",
275
+ text: "Hello World"
276
+ }
277
+ ```
278
+
279
+ #### 使用示例
280
+
281
+ ```javascript
282
+ // 示例 1: 获取标题文本
283
+ const title = await page_query({
284
+ selector: ".page-title",
285
+ save: true
286
+ })
287
+ const result = await element_get_text({ refId: title.refId })
288
+ console.log(result.text) // "商品详情"
289
+
290
+ // 示例 2: 获取列表所有文本
291
+ const items = await page_query_all({
292
+ selector: ".item-title",
293
+ save: true
294
+ })
295
+ const titles = []
296
+ for (const item of items.elements) {
297
+ const text = await element_get_text({ refId: item.refId })
298
+ titles.push(text.text)
299
+ }
300
+ console.log("商品列表:", titles)
301
+
302
+ // 示例 3: 文本验证
303
+ const price = await page_query({
304
+ selector: ".price",
305
+ save: true
306
+ })
307
+ const result = await element_get_text({ refId: price.refId })
308
+ if (result.text.includes("¥")) {
309
+ console.log("价格格式正确")
310
+ }
311
+
312
+ // 示例 4: 获取嵌套文本
313
+ const card = await page_query({
314
+ selector: ".user-card",
315
+ save: true
316
+ })
317
+ const text = await element_get_text({ refId: card.refId })
318
+ console.log(text.text) // 包含所有子元素文本
319
+ ```
320
+
321
+ #### 注意事项
322
+
323
+ - 💡 **包含子元素**: 返回的文本包含所有子元素的文本
324
+ - 💡 **空白字符**: 可能包含前后空白字符
325
+ - ⚠️ **不可见元素**: 即使元素不可见也能获取文本
326
+
327
+ ---
328
+
329
+ ### element_get_value
330
+
331
+ 获取输入框或文本域的当前值。
332
+
333
+ #### 参数
334
+
335
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
336
+ |--------|------|------|--------|------|
337
+ | `refId` | string | ✅ | - | 元素引用 ID |
338
+
339
+ #### 返回值
340
+
341
+ ```typescript
342
+ {
343
+ success: true,
344
+ message: "Element value retrieved",
345
+ value: "user input text"
346
+ }
347
+ ```
348
+
349
+ #### 使用示例
350
+
351
+ ```javascript
352
+ // 示例 1: 获取输入框值
353
+ const input = await page_query({
354
+ selector: "#email",
355
+ save: true
356
+ })
357
+ const result = await element_get_value({ refId: input.refId })
358
+ console.log("邮箱:", result.value)
359
+
360
+ // 示例 2: 验证表单数据
361
+ await element_input({ refId: username.refId, value: "alice" })
362
+ const check = await element_get_value({ refId: username.refId })
363
+ if (check.value === "alice") {
364
+ console.log("✅ 输入成功")
365
+ }
366
+
367
+ // 示例 3: 获取初始值
368
+ // 未输入时返回空字符串或默认值
369
+ const result = await element_get_value({ refId: input.refId })
370
+ console.log(result.value) // "" 或默认值
371
+ ```
372
+
373
+ #### 注意事项
374
+
375
+ - ⚠️ **仅限输入组件**: 仅适用于 `input`, `textarea`, `picker` 等表单组件
376
+ - 💡 **非文本元素**: 对于非表单元素,使用 `element_get_text`
377
+
378
+ ---
379
+
380
+ ### element_get_attribute
381
+
382
+ 获取元素的 HTML 属性值(attribute)。
383
+
384
+ #### 参数
385
+
386
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
387
+ |--------|------|------|--------|------|
388
+ | `refId` | string | ✅ | - | 元素引用 ID |
389
+ | `name` | string | ✅ | - | 属性名称(如 `class`, `id`, `data-id`) |
390
+
391
+ #### 返回值
392
+
393
+ ```typescript
394
+ {
395
+ success: true,
396
+ message: "Attribute \"class\" retrieved",
397
+ value: "btn primary"
398
+ }
399
+ ```
400
+
401
+ #### 使用示例
402
+
403
+ ```javascript
404
+ // 示例 1: 获取 class 属性
405
+ const btn = await page_query({
406
+ selector: ".submit-btn",
407
+ save: true
408
+ })
409
+ const result = await element_get_attribute({
410
+ refId: btn.refId,
411
+ name: "class"
412
+ })
413
+ console.log(result.value) // "submit-btn primary"
414
+
415
+ // 示例 2: 获取自定义属性
416
+ const item = await page_query({
417
+ selector: ".product-item",
418
+ save: true
419
+ })
420
+ const productId = await element_get_attribute({
421
+ refId: item.refId,
422
+ name: "data-id"
423
+ })
424
+ console.log("商品 ID:", productId.value)
425
+
426
+ // 示例 3: 检查属性是否存在
427
+ const result = await element_get_attribute({
428
+ refId: el.refId,
429
+ name: "disabled"
430
+ })
431
+ if (result.value !== null) {
432
+ console.log("按钮已禁用")
433
+ }
434
+ ```
435
+
436
+ #### 注意事项
437
+
438
+ - 💡 **Attribute vs Property**: 属性(attribute)是 HTML 标签上的,属性(property)是 DOM 对象的
439
+ - 💡 **不存在返回 null**: 如果属性不存在,返回 `null`
440
+ - 💡 **小程序专有属性**: 支持小程序组件的自定义属性(如 `data-*`)
441
+
442
+ ---
443
+
444
+ ### element_get_property
445
+
446
+ 获取元素的 DOM 属性值(property)。
447
+
448
+ #### 参数
449
+
450
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
451
+ |--------|------|------|--------|------|
452
+ | `refId` | string | ✅ | - | 元素引用 ID |
453
+ | `name` | string | ✅ | - | 属性名称(如 `value`, `checked`, `dataset`) |
454
+
455
+ #### 返回值
456
+
457
+ ```typescript
458
+ {
459
+ success: true,
460
+ message: "Property \"checked\" retrieved",
461
+ value: true // 可能是任意类型
462
+ }
463
+ ```
464
+
465
+ #### 使用示例
466
+
467
+ ```javascript
468
+ // 示例 1: 获取 checkbox 状态
469
+ const checkbox = await page_query({
470
+ selector: "checkbox",
471
+ save: true
472
+ })
473
+ const result = await element_get_property({
474
+ refId: checkbox.refId,
475
+ name: "checked"
476
+ })
477
+ console.log("选中状态:", result.value) // true/false
478
+
479
+ // 示例 2: 获取 dataset
480
+ const item = await page_query({
481
+ selector: ".item",
482
+ save: true
483
+ })
484
+ const dataset = await element_get_property({
485
+ refId: item.refId,
486
+ name: "dataset"
487
+ })
488
+ console.log("数据集:", dataset.value) // { id: "123", type: "product" }
489
+
490
+ // 示例 3: 获取 value(property 形式)
491
+ const input = await page_query({
492
+ selector: "input",
493
+ save: true
494
+ })
495
+ const result = await element_get_property({
496
+ refId: input.refId,
497
+ name: "value"
498
+ })
499
+ console.log(result.value)
500
+ ```
501
+
502
+ #### 注意事项
503
+
504
+ - 💡 **返回类型**: 可能返回任意 JavaScript 类型(string, number, boolean, object, array)
505
+ - 💡 **与 attribute 的区别**: property 是 DOM 对象的运行时状态,attribute 是 HTML 标签的静态定义
506
+
507
+ ---
508
+
509
+ ### element_get_style
510
+
511
+ 获取元素的样式值。
512
+
513
+ #### 参数
514
+
515
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
516
+ |--------|------|------|--------|------|
517
+ | `refId` | string | ✅ | - | 元素引用 ID |
518
+ | `name` | string | ✅ | - | 样式属性名(如 `color`, `fontSize`, `display`) |
519
+
520
+ #### 返回值
521
+
522
+ ```typescript
523
+ {
524
+ success: true,
525
+ message: "Style \"color\" retrieved",
526
+ value: "rgb(255, 0, 0)"
527
+ }
528
+ ```
529
+
530
+ #### 使用示例
531
+
532
+ ```javascript
533
+ // 示例 1: 获取颜色
534
+ const title = await page_query({
535
+ selector: ".title",
536
+ save: true
537
+ })
538
+ const color = await element_get_style({
539
+ refId: title.refId,
540
+ name: "color"
541
+ })
542
+ console.log("标题颜色:", color.value) // "rgb(255, 0, 0)"
543
+
544
+ // 示例 2: 检查显示状态
545
+ const modal = await page_query({
546
+ selector: ".modal",
547
+ save: true
548
+ })
549
+ const display = await element_get_style({
550
+ refId: modal.refId,
551
+ name: "display"
552
+ })
553
+ if (display.value === "none") {
554
+ console.log("模态框已隐藏")
555
+ }
556
+
557
+ // 示例 3: 获取字体大小
558
+ const fontSize = await element_get_style({
559
+ refId: el.refId,
560
+ name: "fontSize"
561
+ })
562
+ console.log(fontSize.value) // "16px"
563
+ ```
564
+
565
+ #### 注意事项
566
+
567
+ - 💡 **计算样式**: 返回的是计算后的样式值,包含继承和默认值
568
+ - 💡 **单位**: 数值类型样式会包含单位(如 `16px`, `1.5em`)
569
+ - ⚠️ **驼峰命名**: 使用 JavaScript 驼峰命名(`fontSize` 而非 `font-size`)
570
+
571
+ ---
572
+
573
+ ### element_get_size
574
+
575
+ 获取元素的宽度和高度(尺寸)。
576
+
577
+ #### 参数
578
+
579
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
580
+ |--------|------|------|--------|------|
581
+ | `refId` | string | ✅ | - | 元素引用 ID |
582
+
583
+ #### 返回值
584
+
585
+ ```typescript
586
+ {
587
+ success: true,
588
+ message: "Element size retrieved",
589
+ size: {
590
+ width: 375, // 宽度(px)
591
+ height: 50 // 高度(px)
592
+ }
593
+ }
594
+ ```
595
+
596
+ #### 使用示例
597
+
598
+ ```javascript
599
+ // 示例 1: 获取按钮尺寸
600
+ const btn = await page_query({
601
+ selector: ".submit-btn",
602
+ save: true
603
+ })
604
+ const size = await element_get_size({ refId: btn.refId })
605
+ console.log(`按钮尺寸: ${size.size.width}x${size.size.height}`)
606
+
607
+ // 示例 2: 验证布局
608
+ const card = await page_query({
609
+ selector: ".card",
610
+ save: true
611
+ })
612
+ const { size } = await element_get_size({ refId: card.refId })
613
+ if (size.width < 300) {
614
+ console.warn("卡片宽度不足")
615
+ }
616
+
617
+ // 示例 3: 计算宽高比
618
+ const image = await page_query({
619
+ selector: ".product-image",
620
+ save: true
621
+ })
622
+ const { size } = await element_get_size({ refId: image.refId })
623
+ const ratio = size.width / size.height
624
+ console.log("图片宽高比:", ratio.toFixed(2))
625
+ ```
626
+
627
+ #### 注意事项
628
+
629
+ - 💡 **单位**: 返回值单位为物理像素(px)
630
+ - 💡 **包含 padding 和 border**: 返回的是元素的外部尺寸
631
+ - ⚠️ **不可见元素**: 不可见元素(display: none)宽高为 0
632
+
633
+ ---
634
+
635
+ ### element_get_offset
636
+
637
+ 获取元素相对于页面的偏移位置。
638
+
639
+ #### 参数
640
+
641
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
642
+ |--------|------|------|--------|------|
643
+ | `refId` | string | ✅ | - | 元素引用 ID |
644
+
645
+ #### 返回值
646
+
647
+ ```typescript
648
+ {
649
+ success: true,
650
+ message: "Element offset retrieved",
651
+ offset: {
652
+ left: 20, // 左偏移(px)
653
+ top: 100 // 上偏移(px)
654
+ }
655
+ }
656
+ ```
657
+
658
+ #### 使用示例
659
+
660
+ ```javascript
661
+ // 示例 1: 获取元素位置
662
+ const element = await page_query({
663
+ selector: ".floating-btn",
664
+ save: true
665
+ })
666
+ const offset = await element_get_offset({ refId: element.refId })
667
+ console.log(`位置: (${offset.offset.left}, ${offset.offset.top})`)
668
+
669
+ // 示例 2: 判断元素是否在视口内
670
+ const { offset } = await element_get_offset({ refId: el.refId })
671
+ const { size } = await page_get_size()
672
+ const isVisible = offset.top >= 0 && offset.top < size.height
673
+ console.log("是否可见:", isVisible)
674
+
675
+ // 示例 3: 计算两个元素的相对位置
676
+ const offset1 = await element_get_offset({ refId: el1.refId })
677
+ const offset2 = await element_get_offset({ refId: el2.refId })
678
+ const distance = Math.abs(offset2.offset.top - offset1.offset.top)
679
+ console.log("垂直距离:", distance)
680
+ ```
681
+
682
+ #### 注意事项
683
+
684
+ - 💡 **相对于页面**: 偏移量是相对于页面左上角,不是视口
685
+ - 💡 **滚动影响**: 不受页面滚动影响(始终相对页面原点)
686
+ - ⚠️ **定位元素**: position: fixed 元素的 offset 可能不符合预期
687
+
688
+ ---
689
+
690
+ ## 触摸事件
691
+
692
+ ### element_touchstart
693
+
694
+ 触发触摸开始事件。
695
+
696
+ #### 参数
697
+
698
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
699
+ |--------|------|------|--------|------|
700
+ | `refId` | string | ✅ | - | 元素引用 ID |
701
+ | `touches` | array | ✅ | - | 当前屏幕上的触摸点数组 |
702
+ | `changedTouches` | array | ✅ | - | 变化的触摸点数组 |
703
+
704
+ #### 返回值
705
+
706
+ ```typescript
707
+ {
708
+ success: true,
709
+ message: "Touch start on element: elem_abc123"
710
+ }
711
+ ```
712
+
713
+ #### 使用示例
714
+
715
+ ```javascript
716
+ // 示例 1: 单点触摸开始
717
+ const element = await page_query({
718
+ selector: ".canvas",
719
+ save: true
720
+ })
721
+ await element_touchstart({
722
+ refId: element.refId,
723
+ touches: [{ identifier: 0, pageX: 100, pageY: 200 }],
724
+ changedTouches: [{ identifier: 0, pageX: 100, pageY: 200 }]
725
+ })
726
+
727
+ // 示例 2: 配合 touchmove 和 touchend 实现滑动
728
+ await element_touchstart({
729
+ refId: el.refId,
730
+ touches: [{ identifier: 0, pageX: 100, pageY: 200 }],
731
+ changedTouches: [{ identifier: 0, pageX: 100, pageY: 200 }]
732
+ })
733
+ await element_touchmove({
734
+ refId: el.refId,
735
+ touches: [{ identifier: 0, pageX: 100, pageY: 100 }],
736
+ changedTouches: [{ identifier: 0, pageX: 100, pageY: 100 }]
737
+ })
738
+ await element_touchend({
739
+ refId: el.refId,
740
+ touches: [],
741
+ changedTouches: [{ identifier: 0, pageX: 100, pageY: 100 }]
742
+ })
743
+ ```
744
+
745
+ #### 注意事项
746
+
747
+ - 💡 **触摸点格式**: `{ identifier: number, pageX: number, pageY: number }`
748
+ - 💡 **多点触摸**: `touches` 可包含多个触摸点
749
+ - ⚠️ **必须配对**: touchstart 必须与 touchmove/touchend 配对使用
750
+
751
+ ---
752
+
753
+ ### element_touchmove
754
+
755
+ 触发触摸移动事件。
756
+
757
+ #### 参数
758
+
759
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
760
+ |--------|------|------|--------|------|
761
+ | `refId` | string | ✅ | - | 元素引用 ID |
762
+ | `touches` | array | ✅ | - | 当前屏幕上的触摸点数组 |
763
+ | `changedTouches` | array | ✅ | - | 变化的触摸点数组 |
764
+
765
+ #### 使用示例
766
+
767
+ ```javascript
768
+ // 示例 1: 拖动元素
769
+ await element_touchstart({ refId, touches: [{ identifier: 0, pageX: 100, pageY: 100 }], changedTouches: [{ identifier: 0, pageX: 100, pageY: 100 }] })
770
+ for (let i = 0; i < 10; i++) {
771
+ await element_touchmove({
772
+ refId,
773
+ touches: [{ identifier: 0, pageX: 100 + i * 10, pageY: 100 }],
774
+ changedTouches: [{ identifier: 0, pageX: 100 + i * 10, pageY: 100 }]
775
+ })
776
+ }
777
+ await element_touchend({ refId, touches: [], changedTouches: [{ identifier: 0, pageX: 200, pageY: 100 }] })
778
+ ```
779
+
780
+ ---
781
+
782
+ ### element_touchend
783
+
784
+ 触发触摸结束事件。
785
+
786
+ #### 参数
787
+
788
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
789
+ |--------|------|------|--------|------|
790
+ | `refId` | string | ✅ | - | 元素引用 ID |
791
+ | `touches` | array | ✅ | - | 当前屏幕上的触摸点数组(通常为空) |
792
+ | `changedTouches` | array | ✅ | - | 变化的触摸点数组 |
793
+
794
+ #### 使用示例
795
+
796
+ ```javascript
797
+ // 示例 1: 完成触摸手势
798
+ await element_touchend({
799
+ refId: el.refId,
800
+ touches: [], // 所有手指离开屏幕
801
+ changedTouches: [{ identifier: 0, pageX: 200, pageY: 100 }]
802
+ })
803
+ ```
804
+
805
+ ---
806
+
807
+ ### element_trigger
808
+
809
+ 触发自定义事件。
810
+
811
+ #### 参数
812
+
813
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
814
+ |--------|------|------|--------|------|
815
+ | `refId` | string | ✅ | - | 元素引用 ID |
816
+ | `type` | string | ✅ | - | 事件类型(如 `change`, `custom-event`) |
817
+ | `detail` | object | ⭐ | {} | 事件详细数据 |
818
+
819
+ #### 返回值
820
+
821
+ ```typescript
822
+ {
823
+ success: true,
824
+ message: "Event \"change\" triggered on element: elem_abc123"
825
+ }
826
+ ```
827
+
828
+ #### 使用示例
829
+
830
+ ```javascript
831
+ // 示例 1: 触发 change 事件
832
+ const picker = await page_query({
833
+ selector: "picker",
834
+ save: true
835
+ })
836
+ await element_trigger({
837
+ refId: picker.refId,
838
+ type: "change",
839
+ detail: { value: 2 }
840
+ })
841
+
842
+ // 示例 2: 触发自定义事件
843
+ const component = await page_query({
844
+ selector: ".custom-component",
845
+ save: true
846
+ })
847
+ await element_trigger({
848
+ refId: component.refId,
849
+ type: "custom-event",
850
+ detail: { action: "submit", data: { id: 123 } }
851
+ })
852
+ ```
853
+
854
+ #### 注意事项
855
+
856
+ - 💡 **小程序事件**: 支持所有小程序标准事件(tap, change, input 等)
857
+ - 💡 **自定义事件**: 可触发组件的自定义事件
858
+ - ⚠️ **事件冒泡**: 会按照小程序事件系统冒泡
859
+
860
+ ---
861
+
862
+ ## 滚动操作(ScrollView)
863
+
864
+ ### element_scroll_to
865
+
866
+ 滚动到指定位置(仅限 ScrollView 组件)。
867
+
868
+ #### 参数
869
+
870
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
871
+ |--------|------|------|--------|------|
872
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 ScrollView) |
873
+ | `x` | number | ✅ | - | 横向滚动位置(px) |
874
+ | `y` | number | ✅ | - | 纵向滚动位置(px) |
875
+
876
+ #### 返回值
877
+
878
+ ```typescript
879
+ {
880
+ success: true,
881
+ message: "Scrolled to (0, 300)"
882
+ }
883
+ ```
884
+
885
+ #### 使用示例
886
+
887
+ ```javascript
888
+ // 示例 1: 滚动到顶部
889
+ const scrollView = await page_query({
890
+ selector: "scroll-view",
891
+ save: true
892
+ })
893
+ await element_scroll_to({
894
+ refId: scrollView.refId,
895
+ x: 0,
896
+ y: 0
897
+ })
898
+
899
+ // 示例 2: 滚动到底部
900
+ const height = await element_scroll_height({ refId: scrollView.refId })
901
+ await element_scroll_to({
902
+ refId: scrollView.refId,
903
+ x: 0,
904
+ y: height.height
905
+ })
906
+
907
+ // 示例 3: 横向滚动
908
+ await element_scroll_to({
909
+ refId: scrollView.refId,
910
+ x: 300,
911
+ y: 0
912
+ })
913
+ ```
914
+
915
+ #### 注意事项
916
+
917
+ - ⚠️ **仅 ScrollView**: 仅适用于 `scroll-view` 组件
918
+ - 💡 **超出范围**: 如果滚动位置超出范围,会滚动到最大/最小值
919
+ - 💡 **动画**: 滚动有动画效果,建议使用 `page_wait_for` 等待
920
+
921
+ ---
922
+
923
+ ### element_scroll_width
924
+
925
+ 获取 ScrollView 的滚动宽度(内容总宽度)。
926
+
927
+ #### 参数
928
+
929
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
930
+ |--------|------|------|--------|------|
931
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 ScrollView) |
932
+
933
+ #### 返回值
934
+
935
+ ```typescript
936
+ {
937
+ success: true,
938
+ message: "Scroll width retrieved",
939
+ width: 800 // 内容总宽度(px)
940
+ }
941
+ ```
942
+
943
+ #### 使用示例
944
+
945
+ ```javascript
946
+ // 示例 1: 获取可滚动宽度
947
+ const scrollView = await page_query({
948
+ selector: "scroll-view",
949
+ save: true
950
+ })
951
+ const result = await element_scroll_width({ refId: scrollView.refId })
952
+ console.log("内容宽度:", result.width)
953
+
954
+ // 示例 2: 判断是否可滚动
955
+ const viewWidth = await element_get_size({ refId: scrollView.refId })
956
+ const contentWidth = await element_scroll_width({ refId: scrollView.refId })
957
+ if (contentWidth.width > viewWidth.size.width) {
958
+ console.log("内容超出,可以滚动")
959
+ }
960
+ ```
961
+
962
+ ---
963
+
964
+ ### element_scroll_height
965
+
966
+ 获取 ScrollView 的滚动高度(内容总高度)。
967
+
968
+ #### 参数
969
+
970
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
971
+ |--------|------|------|--------|------|
972
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 ScrollView) |
973
+
974
+ #### 返回值
975
+
976
+ ```typescript
977
+ {
978
+ success: true,
979
+ message: "Scroll height retrieved",
980
+ height: 1200 // 内容总高度(px)
981
+ }
982
+ ```
983
+
984
+ #### 使用示例
985
+
986
+ ```javascript
987
+ // 示例 1: 滚动到底部
988
+ const height = await element_scroll_height({ refId: scrollView.refId })
989
+ await element_scroll_to({
990
+ refId: scrollView.refId,
991
+ x: 0,
992
+ y: height.height
993
+ })
994
+ ```
995
+
996
+ ---
997
+
998
+ ## 组件特定操作
999
+
1000
+ ### element_swipe_to
1001
+
1002
+ 滑动到指定索引(仅限 Swiper 组件)。
1003
+
1004
+ #### 参数
1005
+
1006
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1007
+ |--------|------|------|--------|------|
1008
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 Swiper) |
1009
+ | `index` | number | ✅ | - | 目标索引(从 0 开始) |
1010
+
1011
+ #### 返回值
1012
+
1013
+ ```typescript
1014
+ {
1015
+ success: true,
1016
+ message: "Swiped to index 2"
1017
+ }
1018
+ ```
1019
+
1020
+ #### 使用示例
1021
+
1022
+ ```javascript
1023
+ // 示例 1: 滑动到第 3 张轮播图
1024
+ const swiper = await page_query({
1025
+ selector: "swiper",
1026
+ save: true
1027
+ })
1028
+ await element_swipe_to({
1029
+ refId: swiper.refId,
1030
+ index: 2 // 索引从 0 开始
1031
+ })
1032
+
1033
+ // 示例 2: 遍历所有轮播图
1034
+ for (let i = 0; i < 5; i++) {
1035
+ await element_swipe_to({ refId: swiper.refId, index: i })
1036
+ await page_wait_for({ timeout: 500 })
1037
+ await miniprogram_screenshot({ filename: `swiper-${i}.png` })
1038
+ }
1039
+ ```
1040
+
1041
+ #### 注意事项
1042
+
1043
+ - ⚠️ **仅 Swiper**: 仅适用于 `swiper` 组件
1044
+ - 💡 **索引范围**: 索引从 0 开始,超出范围不报错但不滚动
1045
+ - 💡 **动画**: 切换有动画效果
1046
+
1047
+ ---
1048
+
1049
+ ### element_move_to
1050
+
1051
+ 移动到指定位置(仅限 MovableView 组件)。
1052
+
1053
+ #### 参数
1054
+
1055
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1056
+ |--------|------|------|--------|------|
1057
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 MovableView) |
1058
+ | `x` | number | ✅ | - | X 坐标(px) |
1059
+ | `y` | number | ✅ | - | Y 坐标(px) |
1060
+
1061
+ #### 返回值
1062
+
1063
+ ```typescript
1064
+ {
1065
+ success: true,
1066
+ message: "Moved to (100, 200)"
1067
+ }
1068
+ ```
1069
+
1070
+ #### 使用示例
1071
+
1072
+ ```javascript
1073
+ // 示例 1: 移动可拖动元素
1074
+ const movable = await page_query({
1075
+ selector: "movable-view",
1076
+ save: true
1077
+ })
1078
+ await element_move_to({
1079
+ refId: movable.refId,
1080
+ x: 100,
1081
+ y: 200
1082
+ })
1083
+ ```
1084
+
1085
+ ---
1086
+
1087
+ ### element_slide_to
1088
+
1089
+ 滑动到指定值(仅限 Slider 组件)。
1090
+
1091
+ #### 参数
1092
+
1093
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1094
+ |--------|------|------|--------|------|
1095
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 Slider) |
1096
+ | `value` | number | ✅ | - | 目标值 |
1097
+
1098
+ #### 返回值
1099
+
1100
+ ```typescript
1101
+ {
1102
+ success: true,
1103
+ message: "Slid to value 50"
1104
+ }
1105
+ ```
1106
+
1107
+ #### 使用示例
1108
+
1109
+ ```javascript
1110
+ // 示例 1: 设置音量滑块
1111
+ const slider = await page_query({
1112
+ selector: ".volume-slider",
1113
+ save: true
1114
+ })
1115
+ await element_slide_to({
1116
+ refId: slider.refId,
1117
+ value: 75 // 音量设为 75%
1118
+ })
1119
+
1120
+ // 示例 2: 调整价格范围
1121
+ await element_slide_to({
1122
+ refId: minPrice.refId,
1123
+ value: 100
1124
+ })
1125
+ await element_slide_to({
1126
+ refId: maxPrice.refId,
1127
+ value: 500
1128
+ })
1129
+ ```
1130
+
1131
+ #### 注意事项
1132
+
1133
+ - ⚠️ **仅 Slider**: 仅适用于 `slider` 组件
1134
+ - 💡 **值范围**: 值必须在 slider 的 min 和 max 范围内
1135
+
1136
+ ---
1137
+
1138
+ ### element_call_context_method
1139
+
1140
+ 调用上下文元素的方法(仅限 ContextElement)。
1141
+
1142
+ #### 参数
1143
+
1144
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1145
+ |--------|------|------|--------|------|
1146
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是 ContextElement) |
1147
+ | `method` | string | ✅ | - | 方法名 |
1148
+ | `args` | array | ⭐ | [] | 方法参数 |
1149
+
1150
+ #### 返回值
1151
+
1152
+ ```typescript
1153
+ {
1154
+ success: true,
1155
+ message: "Context method \"play\" called successfully",
1156
+ result: { success: true } // 方法返回值
1157
+ }
1158
+ ```
1159
+
1160
+ #### 使用示例
1161
+
1162
+ ```javascript
1163
+ // 示例 1: 调用 video 上下文方法
1164
+ const video = await page_query({
1165
+ selector: "video",
1166
+ save: true
1167
+ })
1168
+ await element_call_context_method({
1169
+ refId: video.refId,
1170
+ method: "play"
1171
+ })
1172
+
1173
+ // 示例 2: 调用 canvas 绘图方法
1174
+ const canvas = await page_query({
1175
+ selector: "canvas",
1176
+ save: true
1177
+ })
1178
+ await element_call_context_method({
1179
+ refId: canvas.refId,
1180
+ method: "drawImage",
1181
+ args: ["/images/photo.jpg", 0, 0, 100, 100]
1182
+ })
1183
+ ```
1184
+
1185
+ #### 注意事项
1186
+
1187
+ - ⚠️ **仅 Context 元素**: 仅适用于有上下文的元素(video, canvas, map 等)
1188
+ - 💡 **方法列表**: 参考小程序官方文档查看可用方法
1189
+
1190
+ ---
1191
+
1192
+ ### element_set_data
1193
+
1194
+ 设置自定义组件的数据(仅限 CustomElement)。
1195
+
1196
+ #### 参数
1197
+
1198
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1199
+ |--------|------|------|--------|------|
1200
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是自定义组件) |
1201
+ | `data` | object | ✅ | - | 数据对象 |
1202
+
1203
+ #### 返回值
1204
+
1205
+ ```typescript
1206
+ {
1207
+ success: true,
1208
+ message: "Element data updated with 2 keys"
1209
+ }
1210
+ ```
1211
+
1212
+ #### 使用示例
1213
+
1214
+ ```javascript
1215
+ // 示例 1: 设置自定义组件数据
1216
+ const component = await page_query({
1217
+ selector: "custom-component",
1218
+ save: true
1219
+ })
1220
+ await element_set_data({
1221
+ refId: component.refId,
1222
+ data: {
1223
+ title: "新标题",
1224
+ count: 10
1225
+ }
1226
+ })
1227
+ ```
1228
+
1229
+ ---
1230
+
1231
+ ### element_call_method
1232
+
1233
+ 调用自定义组件的方法(仅限 CustomElement)。
1234
+
1235
+ #### 参数
1236
+
1237
+ | 参数名 | 类型 | 必需 | 默认值 | 描述 |
1238
+ |--------|------|------|--------|------|
1239
+ | `refId` | string | ✅ | - | 元素引用 ID(必须是自定义组件) |
1240
+ | `method` | string | ✅ | - | 方法名 |
1241
+ | `args` | array | ⭐ | [] | 方法参数 |
1242
+
1243
+ #### 返回值
1244
+
1245
+ ```typescript
1246
+ {
1247
+ success: true,
1248
+ message: "Element method \"refresh\" called successfully",
1249
+ result: { success: true }
1250
+ }
1251
+ ```
1252
+
1253
+ #### 使用示例
1254
+
1255
+ ```javascript
1256
+ // 示例 1: 调用自定义组件方法
1257
+ const component = await page_query({
1258
+ selector: "custom-list",
1259
+ save: true
1260
+ })
1261
+ await element_call_method({
1262
+ refId: component.refId,
1263
+ method: "loadMore"
1264
+ })
1265
+
1266
+ // 示例 2: 带参数调用
1267
+ await element_call_method({
1268
+ refId: component.refId,
1269
+ method: "updateItem",
1270
+ args: [123, { name: "新名称" }]
1271
+ })
1272
+ ```
1273
+
1274
+ ---
1275
+
1276
+ ## 完整示例:表单交互测试
1277
+
1278
+ ```javascript
1279
+ // 场景:测试登录表单的完整交互流程
1280
+ async function testLoginForm() {
1281
+ try {
1282
+ // 1. 启动并导航到登录页
1283
+ await miniprogram_launch({ projectPath: "/path/to/project" })
1284
+ await miniprogram_navigate({
1285
+ method: "navigateTo",
1286
+ url: "/pages/login/login"
1287
+ })
1288
+
1289
+ // 2. 查询表单元素
1290
+ const username = await page_query({
1291
+ selector: "#username",
1292
+ save: true
1293
+ })
1294
+ const password = await page_query({
1295
+ selector: "#password",
1296
+ save: true
1297
+ })
1298
+ const submitBtn = await page_query({
1299
+ selector: ".submit-btn",
1300
+ save: true
1301
+ })
1302
+
1303
+ // 3. 填写表单
1304
+ await element_input({
1305
+ refId: username.refId,
1306
+ value: "testuser"
1307
+ })
1308
+ await element_input({
1309
+ refId: password.refId,
1310
+ value: "password123"
1311
+ })
1312
+
1313
+ // 4. 验证输入内容
1314
+ const usernameValue = await element_get_value({ refId: username.refId })
1315
+ console.log("✅ 用户名:", usernameValue.value)
1316
+
1317
+ // 5. 检查按钮状态
1318
+ const btnClass = await element_get_attribute({
1319
+ refId: submitBtn.refId,
1320
+ name: "class"
1321
+ })
1322
+ if (!btnClass.value.includes("disabled")) {
1323
+ console.log("✅ 提交按钮已启用")
1324
+ }
1325
+
1326
+ // 6. 点击提交
1327
+ await element_tap({ refId: submitBtn.refId })
1328
+
1329
+ // 7. 等待并验证结果
1330
+ await page_wait_for({
1331
+ selector: ".success-toast",
1332
+ timeout: 3000
1333
+ })
1334
+ const toast = await page_query({
1335
+ selector: ".success-toast",
1336
+ save: true
1337
+ })
1338
+ const toastText = await element_get_text({ refId: toast.refId })
1339
+ console.log("✅ 登录成功:", toastText.text)
1340
+
1341
+ // 8. 截图
1342
+ await miniprogram_screenshot({
1343
+ filename: "login-success.png"
1344
+ })
1345
+
1346
+ console.log("✅ 表单测试完成")
1347
+
1348
+ } catch (error) {
1349
+ console.error("❌ 测试失败:", error.message)
1350
+ await miniprogram_screenshot({
1351
+ filename: "test-error.png"
1352
+ })
1353
+ throw error
1354
+ } finally {
1355
+ await miniprogram_close()
1356
+ }
1357
+ }
1358
+
1359
+ testLoginForm()
1360
+ ```
1361
+
1362
+ ---
1363
+
1364
+ ## 故障排除
1365
+
1366
+ ### 问题 1: 元素引用失效
1367
+
1368
+ **错误**: `Element not found with refId: elem_xxx`
1369
+
1370
+ **解决方案**:
1371
+ 1. 检查元素是否已被销毁(页面跳转、组件卸载)
1372
+ 2. 使用 `page_query` 重新获取引用
1373
+ 3. 确认 `save: true` 已设置
1374
+
1375
+ ```javascript
1376
+ // ❌ 错误:页面跳转后引用失效
1377
+ const btn = await page_query({ selector: ".btn", save: true })
1378
+ await miniprogram_navigate({ method: "navigateTo", url: "/other" })
1379
+ await element_tap({ refId: btn.refId }) // 错误!
1380
+
1381
+ // ✅ 正确:跳转后重新查询
1382
+ await miniprogram_navigate({ method: "navigateTo", url: "/other" })
1383
+ const newBtn = await page_query({ selector: ".btn", save: true })
1384
+ await element_tap({ refId: newBtn.refId })
1385
+ ```
1386
+
1387
+ ### 问题 2: 组件特定方法调用失败
1388
+
1389
+ **错误**: `Method not supported for this element type`
1390
+
1391
+ **解决方案**:
1392
+ 确认元素类型与方法匹配:
1393
+ - `element_scroll_to` → `scroll-view`
1394
+ - `element_swipe_to` → `swiper`
1395
+ - `element_slide_to` → `slider`
1396
+ - `element_move_to` → `movable-view`
1397
+
1398
+ ```javascript
1399
+ // 检查元素类型
1400
+ const tagName = await element_get_property({
1401
+ refId: el.refId,
1402
+ name: "tagName"
1403
+ })
1404
+ console.log("元素类型:", tagName.value)
1405
+ ```
1406
+
1407
+ ### 问题 3: 触摸事件不生效
1408
+
1409
+ **错误**: 触摸手势没有效果
1410
+
1411
+ **解决方案**:
1412
+ 1. 确保 touchstart/move/end 正确配对
1413
+ 2. 检查触摸点坐标是否在元素范围内
1414
+ 3. 使用 `element_trigger` 作为替代
1415
+
1416
+ ```javascript
1417
+ // ✅ 正确的触摸手势序列
1418
+ await element_touchstart({ refId, touches: [p0], changedTouches: [p0] })
1419
+ await element_touchmove({ refId, touches: [p1], changedTouches: [p1] })
1420
+ await element_touchend({ refId, touches: [], changedTouches: [p1] })
1421
+ ```
1422
+
1423
+ ---
1424
+
1425
+ ## 技术细节
1426
+
1427
+ ### 元素引用生命周期
1428
+
1429
+ - **创建**: `page_query({ save: true })` 时创建
1430
+ - **存储**: 存储在 Session 的 `elements` Map 中
1431
+ - **失效**: 页面销毁、会话超时(30 分钟)、`miniprogram_close()`
1432
+ - **清理**: `miniprogram_disconnect()` 保留,`miniprogram_close()` 清除
1433
+
1434
+ ### 坐标系统
1435
+
1436
+ - **物理像素**: 所有坐标和尺寸均为物理像素(px),非 rpx
1437
+ - **原点**: 页面左上角为 (0, 0)
1438
+ - **触摸坐标**: `pageX/pageY` 相对于页面,`clientX/clientY` 相对于视口
1439
+
1440
+ ### 性能优化
1441
+
1442
+ - **批量操作**: 使用 `page_query_all` 一次获取多个元素
1443
+ - **引用缓存**: 使用 `save: true` 避免重复查询
1444
+ - **避免频繁截图**: 截图操作较慢,仅在必要时使用
1445
+
1446
+ ---
1447
+
1448
+ **相关文档**:
1449
+ - [Page API](./page.md) - 页面级别操作
1450
+ - [Assert API](./assert.md) - 元素断言验证
1451
+ - [Snapshot API](./snapshot.md) - 元素快照捕获
1452
+ - [使用示例](../../examples/02-form-interaction.md) - 表单交互完整示例
1453
+
1454
+ **最后更新**: 2025-10-02