weixin-devtools-mcp 0.0.1
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 +375 -0
- package/build/index.js +542 -0
- package/build/server.js +200 -0
- package/build/tools/ToolDefinition.js +52 -0
- package/build/tools/assert.js +245 -0
- package/build/tools/connection.js +670 -0
- package/build/tools/console.js +192 -0
- package/build/tools/diagnose.js +348 -0
- package/build/tools/index.js +80 -0
- package/build/tools/input.js +255 -0
- package/build/tools/navigate.js +310 -0
- package/build/tools/network.js +1111 -0
- package/build/tools/page.js +150 -0
- package/build/tools/screenshot.js +47 -0
- package/build/tools/snapshot.js +45 -0
- package/build/tools.js +2131 -0
- package/package.json +58 -0
package/build/server.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 微信开发者工具自动化 MCP 服务器 (模块化版本)
|
|
4
|
+
* 基于 chrome-devtools-mcp 架构模式重构
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { allTools, SimpleToolResponse } from './tools/index.js';
|
|
10
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
11
|
+
/**
|
|
12
|
+
* 全局上下文状态
|
|
13
|
+
*/
|
|
14
|
+
const globalContext = {
|
|
15
|
+
miniProgram: null,
|
|
16
|
+
currentPage: null,
|
|
17
|
+
elementMap: new Map(),
|
|
18
|
+
consoleStorage: {
|
|
19
|
+
consoleMessages: [],
|
|
20
|
+
exceptionMessages: [],
|
|
21
|
+
isMonitoring: false,
|
|
22
|
+
startTime: null,
|
|
23
|
+
},
|
|
24
|
+
networkStorage: {
|
|
25
|
+
requests: [],
|
|
26
|
+
isMonitoring: false,
|
|
27
|
+
startTime: null,
|
|
28
|
+
originalMethods: {},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* 创建 MCP 服务器
|
|
33
|
+
*/
|
|
34
|
+
const server = new Server({
|
|
35
|
+
name: "weixin-devtools-mcp",
|
|
36
|
+
version: "0.3.3",
|
|
37
|
+
}, {
|
|
38
|
+
capabilities: {
|
|
39
|
+
resources: {},
|
|
40
|
+
tools: {},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* 工具处理器映射
|
|
45
|
+
*/
|
|
46
|
+
const toolHandlers = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* 注册工具到 MCP 服务器
|
|
49
|
+
*/
|
|
50
|
+
function registerTool(tool) {
|
|
51
|
+
toolHandlers.set(tool.name, tool);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 处理资源列表请求
|
|
55
|
+
*/
|
|
56
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
57
|
+
const resources = [];
|
|
58
|
+
// 连接状态资源
|
|
59
|
+
resources.push({
|
|
60
|
+
uri: "weixin://connection/status",
|
|
61
|
+
mimeType: "application/json",
|
|
62
|
+
name: "连接状态",
|
|
63
|
+
description: "微信开发者工具连接状态"
|
|
64
|
+
});
|
|
65
|
+
// 如果已连接,提供页面快照资源
|
|
66
|
+
if (globalContext.miniProgram && globalContext.currentPage) {
|
|
67
|
+
resources.push({
|
|
68
|
+
uri: "weixin://page/snapshot",
|
|
69
|
+
mimeType: "application/json",
|
|
70
|
+
name: "页面快照",
|
|
71
|
+
description: "当前页面的元素快照"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return { resources };
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* 处理资源读取请求
|
|
78
|
+
*/
|
|
79
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
80
|
+
const url = new URL(request.params.uri);
|
|
81
|
+
if (url.pathname === "/connection/status") {
|
|
82
|
+
const status = {
|
|
83
|
+
connected: !!globalContext.miniProgram,
|
|
84
|
+
hasCurrentPage: !!globalContext.currentPage,
|
|
85
|
+
pagePath: globalContext.currentPage ? await globalContext.currentPage.path : null,
|
|
86
|
+
elementCount: globalContext.elementMap.size
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
contents: [{
|
|
90
|
+
uri: request.params.uri,
|
|
91
|
+
mimeType: "application/json",
|
|
92
|
+
text: JSON.stringify(status, null, 2)
|
|
93
|
+
}]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (url.pathname === "/page/snapshot") {
|
|
97
|
+
if (!globalContext.currentPage) {
|
|
98
|
+
throw new Error("当前没有活动页面");
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
// 这里可以实现获取页面快照的逻辑
|
|
102
|
+
const snapshot = {
|
|
103
|
+
path: await globalContext.currentPage.path,
|
|
104
|
+
elementCount: globalContext.elementMap.size,
|
|
105
|
+
timestamp: new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
contents: [{
|
|
109
|
+
uri: request.params.uri,
|
|
110
|
+
mimeType: "application/json",
|
|
111
|
+
text: JSON.stringify(snapshot, null, 2)
|
|
112
|
+
}]
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error(`获取页面快照失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`未知的资源: ${request.params.uri}`);
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* 处理工具列表请求
|
|
123
|
+
*/
|
|
124
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
125
|
+
const tools = allTools.map(tool => ({
|
|
126
|
+
name: tool.name,
|
|
127
|
+
description: tool.description,
|
|
128
|
+
inputSchema: zodToJsonSchema(tool.schema, {
|
|
129
|
+
strictUnions: true
|
|
130
|
+
}),
|
|
131
|
+
annotations: tool.annotations
|
|
132
|
+
}));
|
|
133
|
+
return { tools };
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* 处理工具调用请求
|
|
137
|
+
*/
|
|
138
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
139
|
+
const toolName = request.params.name;
|
|
140
|
+
const tool = toolHandlers.get(toolName);
|
|
141
|
+
if (!tool) {
|
|
142
|
+
throw new Error(`未知的工具: ${toolName}`);
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
// 验证参数
|
|
146
|
+
const validatedParams = tool.schema.parse(request.params.arguments || {});
|
|
147
|
+
// 创建工具请求和响应对象
|
|
148
|
+
const toolRequest = { params: validatedParams };
|
|
149
|
+
const toolResponse = new SimpleToolResponse();
|
|
150
|
+
// 执行工具处理器
|
|
151
|
+
await tool.handler(toolRequest, toolResponse, globalContext);
|
|
152
|
+
// 构建响应内容
|
|
153
|
+
const content = [];
|
|
154
|
+
// 添加文本响应
|
|
155
|
+
const responseText = toolResponse.getResponseText();
|
|
156
|
+
if (responseText) {
|
|
157
|
+
content.push({
|
|
158
|
+
type: "text",
|
|
159
|
+
text: responseText
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// 添加附加的图片
|
|
163
|
+
const attachedImages = toolResponse.getAttachedImages();
|
|
164
|
+
for (const image of attachedImages) {
|
|
165
|
+
content.push({
|
|
166
|
+
type: "image",
|
|
167
|
+
data: image.data,
|
|
168
|
+
mimeType: image.mimeType
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return { content };
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
175
|
+
return {
|
|
176
|
+
content: [{
|
|
177
|
+
type: "text",
|
|
178
|
+
text: `工具执行失败: ${errorMessage}`
|
|
179
|
+
}],
|
|
180
|
+
isError: true
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* 注册所有工具
|
|
186
|
+
*/
|
|
187
|
+
for (const tool of allTools) {
|
|
188
|
+
registerTool(tool);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 启动服务器
|
|
192
|
+
*/
|
|
193
|
+
async function main() {
|
|
194
|
+
const transport = new StdioServerTransport();
|
|
195
|
+
await server.connect(transport);
|
|
196
|
+
}
|
|
197
|
+
main().catch((error) => {
|
|
198
|
+
console.error("Server error:", error);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具定义基础框架
|
|
3
|
+
* 参考 chrome-devtools-mcp 的设计模式
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 工具分类枚举
|
|
7
|
+
*/
|
|
8
|
+
export var ToolCategories;
|
|
9
|
+
(function (ToolCategories) {
|
|
10
|
+
ToolCategories["CONNECTION"] = "Connection";
|
|
11
|
+
ToolCategories["PAGE_INTERACTION"] = "Page interaction";
|
|
12
|
+
ToolCategories["AUTOMATION"] = "Automation";
|
|
13
|
+
ToolCategories["DEBUGGING"] = "Debugging";
|
|
14
|
+
})(ToolCategories || (ToolCategories = {}));
|
|
15
|
+
/**
|
|
16
|
+
* 定义工具的辅助函数
|
|
17
|
+
*/
|
|
18
|
+
export function defineTool(definition) {
|
|
19
|
+
return {
|
|
20
|
+
name: definition.name,
|
|
21
|
+
description: definition.description,
|
|
22
|
+
schema: definition.schema,
|
|
23
|
+
annotations: definition.annotations,
|
|
24
|
+
handler: definition.handler,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 简单的响应实现类
|
|
29
|
+
*/
|
|
30
|
+
export class SimpleToolResponse {
|
|
31
|
+
responseLines = [];
|
|
32
|
+
includeSnapshot = false;
|
|
33
|
+
attachedImages = [];
|
|
34
|
+
appendResponseLine(text) {
|
|
35
|
+
this.responseLines.push(text);
|
|
36
|
+
}
|
|
37
|
+
setIncludeSnapshot(include) {
|
|
38
|
+
this.includeSnapshot = include;
|
|
39
|
+
}
|
|
40
|
+
attachImage(data, mimeType) {
|
|
41
|
+
this.attachedImages.push({ data, mimeType });
|
|
42
|
+
}
|
|
43
|
+
getResponseText() {
|
|
44
|
+
return this.responseLines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
shouldIncludeSnapshot() {
|
|
47
|
+
return this.includeSnapshot;
|
|
48
|
+
}
|
|
49
|
+
getAttachedImages() {
|
|
50
|
+
return this.attachedImages;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 断言验证工具
|
|
3
|
+
* 提供各种元素状态和内容的断言验证功能
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { defineTool } from './ToolDefinition.js';
|
|
7
|
+
import { assertElementExists, assertElementVisible, assertElementText, assertElementAttribute } from '../tools.js';
|
|
8
|
+
/**
|
|
9
|
+
* 断言元素存在
|
|
10
|
+
*/
|
|
11
|
+
export const assertExistsTool = defineTool({
|
|
12
|
+
name: 'assert_exists',
|
|
13
|
+
description: '断言元素存在或不存在',
|
|
14
|
+
schema: z.object({
|
|
15
|
+
selector: z.string().optional().describe('CSS选择器'),
|
|
16
|
+
uid: z.string().optional().describe('元素UID'),
|
|
17
|
+
shouldExist: z.boolean().describe('期望存在状态,true为存在,false为不存在'),
|
|
18
|
+
timeout: z.number().optional().default(5000).describe('等待超时时间(毫秒),默认5000ms'),
|
|
19
|
+
}),
|
|
20
|
+
annotations: {
|
|
21
|
+
audience: ['developers'],
|
|
22
|
+
},
|
|
23
|
+
handler: async (request, response, context) => {
|
|
24
|
+
const { selector, uid, shouldExist, timeout } = request.params;
|
|
25
|
+
if (!selector && !uid) {
|
|
26
|
+
throw new Error('必须提供selector或uid参数');
|
|
27
|
+
}
|
|
28
|
+
if (!context.currentPage) {
|
|
29
|
+
throw new Error('请先获取当前页面');
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const options = {
|
|
33
|
+
selector,
|
|
34
|
+
uid,
|
|
35
|
+
shouldExist,
|
|
36
|
+
timeout
|
|
37
|
+
};
|
|
38
|
+
const result = await assertElementExists(context.currentPage, options);
|
|
39
|
+
// 根据断言结果返回信息
|
|
40
|
+
response.appendResponseLine(`断言结果: ${result.passed ? '通过' : '失败'}`);
|
|
41
|
+
response.appendResponseLine(`消息: ${result.message}`);
|
|
42
|
+
response.appendResponseLine(`期望: ${result.expected}`);
|
|
43
|
+
response.appendResponseLine(`实际: ${result.actual}`);
|
|
44
|
+
response.appendResponseLine(`时间戳: ${new Date(result.timestamp).toISOString()}`);
|
|
45
|
+
if (!result.passed) {
|
|
46
|
+
throw new Error(`断言失败: ${result.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
51
|
+
response.appendResponseLine(`断言执行失败: ${errorMessage}`);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* 断言元素可见性
|
|
58
|
+
*/
|
|
59
|
+
export const assertVisibleTool = defineTool({
|
|
60
|
+
name: 'assert_visible',
|
|
61
|
+
description: '断言元素可见或不可见',
|
|
62
|
+
schema: z.object({
|
|
63
|
+
uid: z.string().describe('元素UID'),
|
|
64
|
+
visible: z.boolean().describe('期望可见状态,true为可见,false为不可见'),
|
|
65
|
+
}),
|
|
66
|
+
annotations: {
|
|
67
|
+
audience: ['developers'],
|
|
68
|
+
},
|
|
69
|
+
handler: async (request, response, context) => {
|
|
70
|
+
const { uid, visible } = request.params;
|
|
71
|
+
if (!context.currentPage) {
|
|
72
|
+
throw new Error('请先获取当前页面');
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const options = { uid, visible };
|
|
76
|
+
const result = await assertElementVisible(context.currentPage, context.elementMap, options);
|
|
77
|
+
// 根据断言结果返回信息
|
|
78
|
+
response.appendResponseLine(`断言结果: ${result.passed ? '通过' : '失败'}`);
|
|
79
|
+
response.appendResponseLine(`消息: ${result.message}`);
|
|
80
|
+
response.appendResponseLine(`期望: ${result.expected ? '可见' : '不可见'}`);
|
|
81
|
+
response.appendResponseLine(`实际: ${result.actual ? '可见' : '不可见'}`);
|
|
82
|
+
response.appendResponseLine(`时间戳: ${new Date(result.timestamp).toISOString()}`);
|
|
83
|
+
if (!result.passed) {
|
|
84
|
+
throw new Error(`断言失败: ${result.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
|
+
response.appendResponseLine(`断言执行失败: ${errorMessage}`);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* 断言元素文本内容
|
|
96
|
+
*/
|
|
97
|
+
export const assertTextTool = defineTool({
|
|
98
|
+
name: 'assert_text',
|
|
99
|
+
description: '断言元素文本内容',
|
|
100
|
+
schema: z.object({
|
|
101
|
+
uid: z.string().describe('元素UID'),
|
|
102
|
+
text: z.string().optional().describe('精确匹配的文本'),
|
|
103
|
+
textContains: z.string().optional().describe('包含的文本'),
|
|
104
|
+
textMatches: z.string().optional().describe('正则表达式匹配'),
|
|
105
|
+
}),
|
|
106
|
+
annotations: {
|
|
107
|
+
audience: ['developers'],
|
|
108
|
+
},
|
|
109
|
+
handler: async (request, response, context) => {
|
|
110
|
+
const { uid, text, textContains, textMatches } = request.params;
|
|
111
|
+
if (!text && !textContains && !textMatches) {
|
|
112
|
+
throw new Error('必须指定text、textContains或textMatches参数之一');
|
|
113
|
+
}
|
|
114
|
+
if (!context.currentPage) {
|
|
115
|
+
throw new Error('请先获取当前页面');
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const options = {
|
|
119
|
+
uid,
|
|
120
|
+
text,
|
|
121
|
+
textContains,
|
|
122
|
+
textMatches
|
|
123
|
+
};
|
|
124
|
+
const result = await assertElementText(context.currentPage, context.elementMap, options);
|
|
125
|
+
// 根据断言结果返回信息
|
|
126
|
+
response.appendResponseLine(`断言结果: ${result.passed ? '通过' : '失败'}`);
|
|
127
|
+
response.appendResponseLine(`消息: ${result.message}`);
|
|
128
|
+
response.appendResponseLine(`期望: ${result.expected}`);
|
|
129
|
+
response.appendResponseLine(`实际: ${result.actual}`);
|
|
130
|
+
response.appendResponseLine(`时间戳: ${new Date(result.timestamp).toISOString()}`);
|
|
131
|
+
if (!result.passed) {
|
|
132
|
+
throw new Error(`断言失败: ${result.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
137
|
+
response.appendResponseLine(`断言执行失败: ${errorMessage}`);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* 断言元素属性
|
|
144
|
+
*/
|
|
145
|
+
export const assertAttributeTool = defineTool({
|
|
146
|
+
name: 'assert_attribute',
|
|
147
|
+
description: '断言元素属性值',
|
|
148
|
+
schema: z.object({
|
|
149
|
+
uid: z.string().describe('元素UID'),
|
|
150
|
+
attributeKey: z.string().describe('属性名'),
|
|
151
|
+
attributeValue: z.string().describe('期望的属性值'),
|
|
152
|
+
}),
|
|
153
|
+
annotations: {
|
|
154
|
+
audience: ['developers'],
|
|
155
|
+
},
|
|
156
|
+
handler: async (request, response, context) => {
|
|
157
|
+
const { uid, attributeKey, attributeValue } = request.params;
|
|
158
|
+
if (!context.currentPage) {
|
|
159
|
+
throw new Error('请先获取当前页面');
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const options = {
|
|
163
|
+
uid,
|
|
164
|
+
attribute: {
|
|
165
|
+
key: attributeKey,
|
|
166
|
+
value: attributeValue
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const result = await assertElementAttribute(context.currentPage, context.elementMap, options);
|
|
170
|
+
// 根据断言结果返回信息
|
|
171
|
+
response.appendResponseLine(`断言结果: ${result.passed ? '通过' : '失败'}`);
|
|
172
|
+
response.appendResponseLine(`消息: ${result.message}`);
|
|
173
|
+
response.appendResponseLine(`期望: ${result.expected}`);
|
|
174
|
+
response.appendResponseLine(`实际: ${result.actual}`);
|
|
175
|
+
response.appendResponseLine(`时间戳: ${new Date(result.timestamp).toISOString()}`);
|
|
176
|
+
if (!result.passed) {
|
|
177
|
+
throw new Error(`断言失败: ${result.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
182
|
+
response.appendResponseLine(`断言执行失败: ${errorMessage}`);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* 断言元素状态(通用)
|
|
189
|
+
*/
|
|
190
|
+
export const assertStateTool = defineTool({
|
|
191
|
+
name: 'assert_state',
|
|
192
|
+
description: '断言元素的各种状态',
|
|
193
|
+
schema: z.object({
|
|
194
|
+
uid: z.string().describe('元素UID'),
|
|
195
|
+
visible: z.boolean().optional().describe('期望可见状态'),
|
|
196
|
+
enabled: z.boolean().optional().describe('期望启用状态'),
|
|
197
|
+
checked: z.boolean().optional().describe('期望选中状态(checkbox/radio)'),
|
|
198
|
+
focused: z.boolean().optional().describe('期望焦点状态'),
|
|
199
|
+
}),
|
|
200
|
+
annotations: {
|
|
201
|
+
audience: ['developers'],
|
|
202
|
+
},
|
|
203
|
+
handler: async (request, response, context) => {
|
|
204
|
+
const { uid, visible, enabled, checked, focused } = request.params;
|
|
205
|
+
if (visible === undefined && enabled === undefined && checked === undefined && focused === undefined) {
|
|
206
|
+
throw new Error('必须指定至少一个状态参数');
|
|
207
|
+
}
|
|
208
|
+
if (!context.currentPage) {
|
|
209
|
+
throw new Error('请先获取当前页面');
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const results = [];
|
|
213
|
+
// 可见性断言
|
|
214
|
+
if (visible !== undefined) {
|
|
215
|
+
const options = { uid, visible };
|
|
216
|
+
const result = await assertElementVisible(context.currentPage, context.elementMap, options);
|
|
217
|
+
results.push(result);
|
|
218
|
+
}
|
|
219
|
+
// TODO: 这里可以添加更多状态断言,如enabled、checked、focused
|
|
220
|
+
// 目前只实现了visible,其他状态需要扩展底层函数
|
|
221
|
+
// 汇总结果
|
|
222
|
+
const allPassed = results.every(r => r.passed);
|
|
223
|
+
const failedResults = results.filter(r => !r.passed);
|
|
224
|
+
response.appendResponseLine(`断言结果: ${allPassed ? '全部通过' : '部分失败'}`);
|
|
225
|
+
response.appendResponseLine(`检查项数: ${results.length}`);
|
|
226
|
+
response.appendResponseLine(`通过项数: ${results.filter(r => r.passed).length}`);
|
|
227
|
+
response.appendResponseLine(`失败项数: ${failedResults.length}`);
|
|
228
|
+
if (failedResults.length > 0) {
|
|
229
|
+
response.appendResponseLine('');
|
|
230
|
+
response.appendResponseLine('失败详情:');
|
|
231
|
+
failedResults.forEach((result, index) => {
|
|
232
|
+
response.appendResponseLine(`${index + 1}. ${result.message}`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (!allPassed) {
|
|
236
|
+
throw new Error(`状态断言失败: ${failedResults.length}/${results.length} 项失败`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
241
|
+
response.appendResponseLine(`断言执行失败: ${errorMessage}`);
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
});
|