@cqsjjb/meter-sphere-mcp-server 1.0.0-beta.1 → 1.0.0-beta.2
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 +4 -21
- package/mcp-server.mjs +451 -8
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -20,13 +20,13 @@ npm install @cqsjjb/meter-sphere-mcp-server
|
|
|
20
20
|
|
|
21
21
|
在 Cursor 的 MCP 配置文件中添加以下配置(通常在 `~/.cursor/mcp.json` 或 Cursor 设置中):
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**使用 npx(推荐,无需本地安装):**
|
|
24
24
|
```json
|
|
25
25
|
{
|
|
26
26
|
"mcpServers": {
|
|
27
27
|
"meter-sphere": {
|
|
28
|
-
"command": "
|
|
29
|
-
"args": ["
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "@cqsjjb/meter-sphere-mcp-server"],
|
|
30
30
|
"env": {
|
|
31
31
|
"PLATFORM_URL": "http://your-platform-url?organization=xxx&project=xxx&testPlanId=xxx",
|
|
32
32
|
"X_AUTH_TOKEN": "your-x-auth-token-here",
|
|
@@ -41,7 +41,7 @@ npm install @cqsjjb/meter-sphere-mcp-server
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
**使用本地安装(如果已通过 npm install 安装):**
|
|
45
45
|
```json
|
|
46
46
|
{
|
|
47
47
|
"mcpServers": {
|
|
@@ -62,23 +62,6 @@ npm install @cqsjjb/meter-sphere-mcp-server
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
**使用全局安装:**
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"mcpServers": {
|
|
69
|
-
"meter-sphere": {
|
|
70
|
-
"command": "npx",
|
|
71
|
-
"args": ["-y", "@cqsjjb/meter-sphere-mcp-server"],
|
|
72
|
-
"env": {
|
|
73
|
-
"PLATFORM_URL": "http://your-platform-url?organization=xxx&project=xxx&testPlanId=xxx",
|
|
74
|
-
"X_AUTH_TOKEN": "your-x-auth-token-here",
|
|
75
|
-
"CSRF_TOKEN": "your-csrf-token-here"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
65
|
## 环境变量说明
|
|
83
66
|
|
|
84
67
|
- `PLATFORM_URL` (必需): 测试用例平台的完整URL(包含查询参数)
|
package/mcp-server.mjs
CHANGED
|
@@ -279,6 +279,25 @@ function getPriorityLevel(priority) {
|
|
|
279
279
|
return priorityMap[priority] !== undefined ? priorityMap[priority] : 999;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* 从AI分析结果中提取判断理由
|
|
284
|
+
*/
|
|
285
|
+
function extractReason(analysisText) {
|
|
286
|
+
// 尝试匹配"判断理由:"或"判断理由:"
|
|
287
|
+
const reasonMatch = analysisText.match(/判断理由[::]\s*([^\n]+(?:\n(?!\d+\.)[^\n]+)*)/i);
|
|
288
|
+
if (reasonMatch) {
|
|
289
|
+
return reasonMatch[1].trim();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 如果没有找到"判断理由",尝试提取"原因"部分
|
|
293
|
+
const causeMatch = analysisText.match(/原因[::]\s*([^\n]+(?:\n(?!\d+\.)[^\n]+)*)/i);
|
|
294
|
+
if (causeMatch) {
|
|
295
|
+
return causeMatch[1].trim();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return 'AI分析结果中未找到明确的判断理由';
|
|
299
|
+
}
|
|
300
|
+
|
|
282
301
|
/**
|
|
283
302
|
* 调用 AI 模型 API 进行流式分析
|
|
284
303
|
*/
|
|
@@ -289,22 +308,42 @@ async function analyzeWithModel(promptText) {
|
|
|
289
308
|
throw new Error('缺少 MODEL_API_KEY 环境变量配置');
|
|
290
309
|
}
|
|
291
310
|
|
|
292
|
-
const analysisPrompt =
|
|
311
|
+
const analysisPrompt = `你是一个专业的测试用例分析专家。请仔细分析以下测试用例,判断其测试方式和优先级。
|
|
293
312
|
|
|
294
313
|
**分类说明:**
|
|
295
314
|
1. **配置优化** - 涉及系统配置、参数设置、选项调整、权限配置、环境配置等
|
|
296
315
|
2. **前端交互功能** - 涉及用户界面操作、页面交互、表单填写、按钮点击、数据展示、样式验证等前端功能
|
|
297
316
|
3. **后端数据** - 涉及接口调用、数据处理、数据库操作、数据同步、API请求、数据查询等后端功能
|
|
298
317
|
|
|
318
|
+
**测试方式判断标准:**
|
|
319
|
+
|
|
320
|
+
**需要浏览器测试(实际运行验证)的情况:**
|
|
321
|
+
- ✅ 涉及接口调用(如:提交表单、调用API、数据请求)
|
|
322
|
+
- 原因:需要验证接口是否真的调用成功,是否返回正确数据,是否处理错误
|
|
323
|
+
- ✅ 涉及表单提交和数据处理流程
|
|
324
|
+
- 原因:需要验证整个流程是否顺畅,是否会卡死,是否有错误提示
|
|
325
|
+
- ✅ 涉及数据展示和列表渲染
|
|
326
|
+
- 原因:需要验证数据是否正确展示,列表是否正确更新
|
|
327
|
+
- ✅ 涉及复杂的UI交互流程(多步骤操作)
|
|
328
|
+
- 原因:需要验证整个流程是否完整,是否有阻塞点
|
|
329
|
+
- ✅ 涉及异步操作和状态变化
|
|
330
|
+
- 原因:需要验证异步操作是否完成,状态是否正确更新
|
|
331
|
+
|
|
332
|
+
**仅需静态代码分析的情况:**
|
|
333
|
+
- ⚠️ 纯代码逻辑检查(如:计算是否正确、数据处理逻辑)
|
|
334
|
+
- ⚠️ 样式验证(CSS样式、布局检查)
|
|
335
|
+
- ⚠️ 代码结构检查(组件是否存在、函数是否定义)
|
|
336
|
+
- ⚠️ 配置项检查(如果只是检查配置项是否存在,不涉及实际调用)
|
|
337
|
+
|
|
299
338
|
**测试用例内容:**
|
|
300
339
|
${promptText}
|
|
301
340
|
|
|
302
341
|
**要求:**
|
|
303
342
|
1. 仔细分析每个测试步骤,判断它主要属于哪个类别
|
|
304
343
|
2. 对于每个测试步骤,明确标注其类别(配置优化/前端交互功能/后端数据)
|
|
305
|
-
3.
|
|
306
|
-
4.
|
|
307
|
-
5.
|
|
344
|
+
3. 判断是否需要浏览器测试(根据上述标准)
|
|
345
|
+
4. 如果某个步骤涉及多个方面,选择最主要的一个类别
|
|
346
|
+
5. 最后给出整体分类结论和测试方式建议
|
|
308
347
|
|
|
309
348
|
**输出格式:**
|
|
310
349
|
请按照以下格式输出:
|
|
@@ -312,9 +351,14 @@ ${promptText}
|
|
|
312
351
|
1. 测试点:[测试点名称]
|
|
313
352
|
2. 用例名称:[用例名称]
|
|
314
353
|
3. 测试类别:[前端交互功能 / 配置优化 / 后端数据]
|
|
315
|
-
4.
|
|
316
|
-
|
|
317
|
-
|
|
354
|
+
4. 是否需要浏览器测试:[是/否]
|
|
355
|
+
5. 判断理由:[详细说明为什么需要或不需要浏览器测试]
|
|
356
|
+
6. 测试步骤
|
|
357
|
+
- 需要测试的步骤:[列出前端相关的步骤]
|
|
358
|
+
- 不需要测试的步骤:[列出非前端相关的步骤]
|
|
359
|
+
7. 测试建议:
|
|
360
|
+
- 如果"是否需要浏览器测试"为"是",建议使用 run_browser_test 工具进行实际运行验证
|
|
361
|
+
- 如果"是否需要浏览器测试"为"否",建议使用静态代码分析(get_test_detail 工具)即可`;
|
|
318
362
|
|
|
319
363
|
return new Promise((resolve, reject) => {
|
|
320
364
|
const urlObj = new URL(`${config.baseURL}/v1/chat/completions`);
|
|
@@ -437,7 +481,19 @@ class MeterSphereMCPServer {
|
|
|
437
481
|
},
|
|
438
482
|
{
|
|
439
483
|
name: 'get_test_detail',
|
|
440
|
-
description:
|
|
484
|
+
description: `根据测试用例ID获取详细信息,包括测试步骤和AI测试提示语。会自动调用AI模型进行测试用例分析。
|
|
485
|
+
|
|
486
|
+
**重要功能:**
|
|
487
|
+
- AI会自动分析测试用例,判断是否需要浏览器测试(实际运行验证)
|
|
488
|
+
- 如果涉及接口调用、表单提交、数据展示等需要实际运行验证的场景,AI会建议使用 run_browser_test 工具
|
|
489
|
+
- 如果仅涉及代码逻辑、样式检查等,AI会建议使用静态代码分析即可
|
|
490
|
+
|
|
491
|
+
**使用建议:**
|
|
492
|
+
1. 先调用此工具获取测试用例详情和AI分析
|
|
493
|
+
2. 根据AI的建议,决定是否需要执行浏览器测试
|
|
494
|
+
3. 如果需要浏览器测试,再调用 run_browser_test 工具
|
|
495
|
+
|
|
496
|
+
配置信息从环境变量读取(PLATFORM_URL、X_AUTH_TOKEN、CSRF_TOKEN、MODEL_BASE_URL、MODEL_API_KEY、MODEL_ID)。若AI生成的提示语中明确提示该测试用例类别为后端数据,你应该跳过该测试用例。`,
|
|
441
497
|
inputSchema: {
|
|
442
498
|
type: 'object',
|
|
443
499
|
properties: {
|
|
@@ -480,6 +536,88 @@ class MeterSphereMCPServer {
|
|
|
480
536
|
required: [],
|
|
481
537
|
},
|
|
482
538
|
},
|
|
539
|
+
{
|
|
540
|
+
name: 'run_browser_test',
|
|
541
|
+
description: `执行浏览器自动化测试,验证前端交互和接口调用是否正常工作。
|
|
542
|
+
|
|
543
|
+
**重要提示:**
|
|
544
|
+
- 此工具需要用户提供 appUrl、API_HOST 和 token
|
|
545
|
+
- appUrl 是应用访问地址,不同环境可能不同(如开发环境、测试环境)
|
|
546
|
+
- API_HOST 和 token 将被设置到浏览器的 sessionStorage 中
|
|
547
|
+
- token 通常是登录后获取的,每次登录可能不同
|
|
548
|
+
- 调用此工具前,必须先询问用户获取 appUrl、API_HOST 和 token
|
|
549
|
+
|
|
550
|
+
**使用流程:**
|
|
551
|
+
1. 当用户要求执行浏览器测试时,必须先询问用户获取以下配置:
|
|
552
|
+
- appUrl:应用访问地址(如 http://localhost:3000)
|
|
553
|
+
- API_HOST:接口域名(如 http://192.168.3.26:8081)
|
|
554
|
+
- token:授权令牌(用户登录后获取)
|
|
555
|
+
2. 获取配置后,再调用此工具执行测试
|
|
556
|
+
|
|
557
|
+
适用于涉及表单提交、接口调用、数据展示等需要实际运行验证的测试用例。`,
|
|
558
|
+
inputSchema: {
|
|
559
|
+
type: 'object',
|
|
560
|
+
properties: {
|
|
561
|
+
testCaseId: {
|
|
562
|
+
type: 'string',
|
|
563
|
+
description: '测试用例ID'
|
|
564
|
+
},
|
|
565
|
+
testPlanCollectionName: {
|
|
566
|
+
type: 'string',
|
|
567
|
+
description: '测试点名称(从get_test_list返回)'
|
|
568
|
+
},
|
|
569
|
+
appUrl: {
|
|
570
|
+
type: 'string',
|
|
571
|
+
description: '应用访问地址(必需)。请向用户询问此配置,例如:http://localhost:3000。不同环境可能有不同的地址(开发环境、测试环境等),每次调用时可能需要不同的地址。'
|
|
572
|
+
},
|
|
573
|
+
sessionStorage: {
|
|
574
|
+
type: 'object',
|
|
575
|
+
description: '需要设置到浏览器sessionStorage中的配置。这些值必须由用户提供,调用此工具前必须先询问用户获取。',
|
|
576
|
+
properties: {
|
|
577
|
+
API_HOST: {
|
|
578
|
+
type: 'string',
|
|
579
|
+
description: '接口域名(必需)。请向用户询问此配置,例如:http://192.168.3.26:8081。此值将被设置到浏览器的 sessionStorage.API_HOST 中。'
|
|
580
|
+
},
|
|
581
|
+
token: {
|
|
582
|
+
type: 'string',
|
|
583
|
+
description: '授权令牌(必需)。请向用户询问此配置,token通常是登录后获取的,每次登录可能不同。此值将被设置到浏览器的 sessionStorage.token 中。'
|
|
584
|
+
},
|
|
585
|
+
organization: {
|
|
586
|
+
type: 'string',
|
|
587
|
+
description: '组织ID(可选)。如果提供,将被设置到 sessionStorage.organization 中。'
|
|
588
|
+
},
|
|
589
|
+
project: {
|
|
590
|
+
type: 'string',
|
|
591
|
+
description: '项目ID(可选)。如果提供,将被设置到 sessionStorage.project 中。'
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
required: ['API_HOST', 'token']
|
|
595
|
+
},
|
|
596
|
+
options: {
|
|
597
|
+
type: 'object',
|
|
598
|
+
description: '测试选项',
|
|
599
|
+
properties: {
|
|
600
|
+
headless: {
|
|
601
|
+
type: 'boolean',
|
|
602
|
+
default: true,
|
|
603
|
+
description: '是否无头模式运行,默认为true'
|
|
604
|
+
},
|
|
605
|
+
timeout: {
|
|
606
|
+
type: 'number',
|
|
607
|
+
default: 30000,
|
|
608
|
+
description: '超时时间(毫秒),默认30000'
|
|
609
|
+
},
|
|
610
|
+
screenshot: {
|
|
611
|
+
type: 'boolean',
|
|
612
|
+
default: true,
|
|
613
|
+
description: '是否截图,默认为true'
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
required: ['testCaseId', 'testPlanCollectionName', 'appUrl', 'sessionStorage']
|
|
619
|
+
}
|
|
620
|
+
},
|
|
483
621
|
],
|
|
484
622
|
};
|
|
485
623
|
});
|
|
@@ -497,6 +635,8 @@ class MeterSphereMCPServer {
|
|
|
497
635
|
return await this.handleMarkTestCompleted(args);
|
|
498
636
|
} else if (name === 'get_test_progress') {
|
|
499
637
|
return await this.handleGetTestProgress(args);
|
|
638
|
+
} else if (name === 'run_browser_test') {
|
|
639
|
+
return await this.handleRunBrowserTest(args);
|
|
500
640
|
} else {
|
|
501
641
|
throw new Error(`未知工具: ${name}`);
|
|
502
642
|
}
|
|
@@ -716,8 +856,23 @@ class MeterSphereMCPServer {
|
|
|
716
856
|
|
|
717
857
|
// 尝试调用AI模型进行分析
|
|
718
858
|
let modelAnalysis = '';
|
|
859
|
+
let testRecommendation = '';
|
|
860
|
+
|
|
719
861
|
try {
|
|
720
862
|
modelAnalysis = await analyzeWithModel(aiPrompt);
|
|
863
|
+
|
|
864
|
+
// 解析AI分析结果,提取是否需要浏览器测试的判断
|
|
865
|
+
if (modelAnalysis.includes('是否需要浏览器测试:是') ||
|
|
866
|
+
modelAnalysis.includes('是否需要浏览器测试: 是') ||
|
|
867
|
+
modelAnalysis.match(/是否需要浏览器测试[::]\s*是/i)) {
|
|
868
|
+
const reason = extractReason(modelAnalysis);
|
|
869
|
+
testRecommendation = `\n\n🔍 **测试方式建议**\n\n此测试用例**需要浏览器测试**(实际运行验证)。\n\n**原因:**\n${reason}\n\n**建议操作:**\n1. 先进行静态代码分析(当前步骤)\n2. 然后使用 \`run_browser_test\` 工具执行浏览器自动化测试,验证:\n - 接口是否真的调用成功\n - 流程是否顺畅,是否会卡死\n - 数据是否正确展示\n - 是否有错误或异常\n\n**注意:** 执行浏览器测试前,需要提供 API_HOST 和 token 配置。`;
|
|
870
|
+
} else if (modelAnalysis.includes('是否需要浏览器测试:否') ||
|
|
871
|
+
modelAnalysis.includes('是否需要浏览器测试: 否') ||
|
|
872
|
+
modelAnalysis.match(/是否需要浏览器测试[::]\s*否/i)) {
|
|
873
|
+
const reason = extractReason(modelAnalysis);
|
|
874
|
+
testRecommendation = `\n\n🔍 **测试方式建议**\n\n此测试用例**仅需静态代码分析**即可。\n\n**原因:**\n${reason}\n\n**建议操作:**\n使用静态代码分析检查代码逻辑、样式、结构等即可,无需实际运行验证。`;
|
|
875
|
+
}
|
|
721
876
|
} catch (error) {
|
|
722
877
|
console.error('AI模型分析失败:', error);
|
|
723
878
|
modelAnalysis = `\n\n⚠️ AI模型分析失败: ${error.message}\n(如果未配置MODEL_API_KEY,可以忽略此错误)`;
|
|
@@ -732,6 +887,11 @@ class MeterSphereMCPServer {
|
|
|
732
887
|
|
|
733
888
|
if (modelAnalysis && !modelAnalysis.includes('AI模型分析失败')) {
|
|
734
889
|
resultText += `\n## AI分析结果\n\n\`\`\`\n${modelAnalysis}\n\`\`\`\n`;
|
|
890
|
+
|
|
891
|
+
// 添加测试方式建议
|
|
892
|
+
if (testRecommendation) {
|
|
893
|
+
resultText += testRecommendation;
|
|
894
|
+
}
|
|
735
895
|
}
|
|
736
896
|
|
|
737
897
|
return {
|
|
@@ -834,6 +994,289 @@ class MeterSphereMCPServer {
|
|
|
834
994
|
};
|
|
835
995
|
}
|
|
836
996
|
|
|
997
|
+
async handleRunBrowserTest(args) {
|
|
998
|
+
const { testCaseId, testPlanCollectionName, appUrl, sessionStorage, options = {} } = args;
|
|
999
|
+
|
|
1000
|
+
// 验证必要配置
|
|
1001
|
+
if (!appUrl) {
|
|
1002
|
+
return {
|
|
1003
|
+
content: [
|
|
1004
|
+
{
|
|
1005
|
+
type: 'text',
|
|
1006
|
+
text: `❌ **缺少必要配置**\n\n请提供以下配置:\n- appUrl: 应用访问地址(如 http://localhost:3000)`
|
|
1007
|
+
}
|
|
1008
|
+
],
|
|
1009
|
+
isError: true
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (!sessionStorage || !sessionStorage.API_HOST || !sessionStorage.token) {
|
|
1014
|
+
return {
|
|
1015
|
+
content: [
|
|
1016
|
+
{
|
|
1017
|
+
type: 'text',
|
|
1018
|
+
text: `❌ **缺少必要配置**\n\n请提供以下配置:\n- API_HOST: 接口域名\n- token: 授权令牌\n\n这些配置将被设置到浏览器的 sessionStorage 中。`
|
|
1019
|
+
}
|
|
1020
|
+
],
|
|
1021
|
+
isError: true
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
// 尝试动态导入 playwright
|
|
1027
|
+
let playwright;
|
|
1028
|
+
try {
|
|
1029
|
+
playwright = await import('playwright');
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
return {
|
|
1032
|
+
content: [
|
|
1033
|
+
{
|
|
1034
|
+
type: 'text',
|
|
1035
|
+
text: `❌ **Playwright 未安装**\n\n浏览器自动化测试功能需要 Playwright 支持。\n\n**安装方法:**\n\`\`\`bash\nnpm install playwright\nnpx playwright install chromium\n\`\`\`\n\n安装完成后,请重新调用此工具。`
|
|
1036
|
+
}
|
|
1037
|
+
],
|
|
1038
|
+
isError: true
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const { chromium } = playwright;
|
|
1043
|
+
|
|
1044
|
+
const browser = await chromium.launch({
|
|
1045
|
+
headless: options.headless !== false,
|
|
1046
|
+
timeout: options.timeout || 30000
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
const context = await browser.newContext();
|
|
1050
|
+
const page = await context.newPage();
|
|
1051
|
+
|
|
1052
|
+
const testResults = {
|
|
1053
|
+
success: false,
|
|
1054
|
+
steps: [],
|
|
1055
|
+
networkRequests: [],
|
|
1056
|
+
errors: [],
|
|
1057
|
+
screenshots: []
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
try {
|
|
1061
|
+
// 在页面加载前设置 sessionStorage
|
|
1062
|
+
await page.addInitScript((storage) => {
|
|
1063
|
+
// 设置 API_HOST 到 sessionStorage
|
|
1064
|
+
if (storage.API_HOST) {
|
|
1065
|
+
sessionStorage.setItem('API_HOST', storage.API_HOST);
|
|
1066
|
+
console.log('✅ 已设置 sessionStorage.API_HOST:', storage.API_HOST);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// 设置 token 到 sessionStorage
|
|
1070
|
+
if (storage.token) {
|
|
1071
|
+
sessionStorage.setItem('token', storage.token);
|
|
1072
|
+
console.log('✅ 已设置 sessionStorage.token');
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// 设置其他可选配置
|
|
1076
|
+
if (storage.organization) {
|
|
1077
|
+
sessionStorage.setItem('organization', storage.organization);
|
|
1078
|
+
}
|
|
1079
|
+
if (storage.project) {
|
|
1080
|
+
sessionStorage.setItem('project', storage.project);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// 验证设置是否成功
|
|
1084
|
+
console.log('SessionStorage 配置验证:', {
|
|
1085
|
+
API_HOST: sessionStorage.getItem('API_HOST'),
|
|
1086
|
+
token: sessionStorage.getItem('token') ? '已设置' : '未设置',
|
|
1087
|
+
organization: sessionStorage.getItem('organization'),
|
|
1088
|
+
project: sessionStorage.getItem('project')
|
|
1089
|
+
});
|
|
1090
|
+
}, sessionStorage);
|
|
1091
|
+
|
|
1092
|
+
// 监听网络请求
|
|
1093
|
+
page.on('request', request => {
|
|
1094
|
+
testResults.networkRequests.push({
|
|
1095
|
+
url: request.url(),
|
|
1096
|
+
method: request.method(),
|
|
1097
|
+
timestamp: new Date().toISOString()
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
page.on('response', response => {
|
|
1102
|
+
const request = testResults.networkRequests.find(r => r.url === response.url());
|
|
1103
|
+
if (request) {
|
|
1104
|
+
request.status = response.status();
|
|
1105
|
+
request.success = response.ok();
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// 监听控制台错误
|
|
1110
|
+
page.on('console', msg => {
|
|
1111
|
+
if (msg.type() === 'error') {
|
|
1112
|
+
testResults.errors.push(`控制台错误: ${msg.text()}`);
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
page.on('pageerror', error => {
|
|
1117
|
+
testResults.errors.push(`页面错误: ${error.message}`);
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
// 导航到应用
|
|
1121
|
+
testResults.steps.push({
|
|
1122
|
+
step: '导航到应用',
|
|
1123
|
+
status: 'running',
|
|
1124
|
+
message: `正在访问 ${appUrl}...`
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
await page.goto(appUrl, {
|
|
1128
|
+
waitUntil: 'networkidle',
|
|
1129
|
+
timeout: options.timeout || 30000
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// 验证 sessionStorage 是否设置成功
|
|
1133
|
+
const apiHostInStorage = await page.evaluate(() => sessionStorage.getItem('API_HOST'));
|
|
1134
|
+
const tokenInStorage = await page.evaluate(() => sessionStorage.getItem('token'));
|
|
1135
|
+
|
|
1136
|
+
if (!apiHostInStorage || !tokenInStorage) {
|
|
1137
|
+
throw new Error(`SessionStorage 配置失败:
|
|
1138
|
+
- API_HOST: ${apiHostInStorage || '未设置'}
|
|
1139
|
+
- token: ${tokenInStorage ? '已设置' : '未设置'}`);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
testResults.steps.push({
|
|
1143
|
+
step: '验证 sessionStorage 配置',
|
|
1144
|
+
status: 'success',
|
|
1145
|
+
message: `✅ API_HOST: ${apiHostInStorage}, Token: 已设置`
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// 获取测试用例详情
|
|
1149
|
+
const platformUrl = process.env.PLATFORM_URL;
|
|
1150
|
+
const xAuthToken = process.env.X_AUTH_TOKEN;
|
|
1151
|
+
const csrfToken = process.env.CSRF_TOKEN;
|
|
1152
|
+
|
|
1153
|
+
if (platformUrl && xAuthToken && csrfToken) {
|
|
1154
|
+
const parsedParams = parseQueryParams(platformUrl);
|
|
1155
|
+
const { organization, project } = parsedParams;
|
|
1156
|
+
|
|
1157
|
+
if (organization && project) {
|
|
1158
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
1159
|
+
const targetUrl = `${apiBaseUrl}/test-plan/functional/case/detail/${testCaseId}`;
|
|
1160
|
+
|
|
1161
|
+
const headers = {
|
|
1162
|
+
'Csrf-token': csrfToken,
|
|
1163
|
+
'organization': organization,
|
|
1164
|
+
'project': project,
|
|
1165
|
+
'x-auth-token': xAuthToken,
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
try {
|
|
1169
|
+
const response = await httpGet(targetUrl, headers);
|
|
1170
|
+
const data = JSON.parse(response);
|
|
1171
|
+
|
|
1172
|
+
if (data && data.data) {
|
|
1173
|
+
const detailData = data.data;
|
|
1174
|
+
const aiPrompt = generateAIPrompt(detailData, testPlanCollectionName);
|
|
1175
|
+
|
|
1176
|
+
testResults.steps.push({
|
|
1177
|
+
step: '获取测试用例详情',
|
|
1178
|
+
status: 'success',
|
|
1179
|
+
message: `✅ 已获取测试用例:${detailData.name || testCaseId}`
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
// 这里可以添加更多的测试步骤执行逻辑
|
|
1183
|
+
// 例如:根据测试用例步骤执行相应的操作
|
|
1184
|
+
testResults.steps.push({
|
|
1185
|
+
step: '执行测试步骤',
|
|
1186
|
+
status: 'info',
|
|
1187
|
+
message: '⚠️ 测试步骤执行功能需要根据具体测试用例内容进行实现。当前版本仅验证了 sessionStorage 配置和页面加载。'
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
testResults.errors.push(`获取测试用例详情失败: ${error.message}`);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// 截图
|
|
1197
|
+
if (options.screenshot !== false) {
|
|
1198
|
+
try {
|
|
1199
|
+
const screenshot = await page.screenshot({ fullPage: true });
|
|
1200
|
+
testResults.screenshots.push(screenshot.toString('base64'));
|
|
1201
|
+
testResults.steps.push({
|
|
1202
|
+
step: '截图',
|
|
1203
|
+
status: 'success',
|
|
1204
|
+
message: '✅ 已保存页面截图'
|
|
1205
|
+
});
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
testResults.errors.push(`截图失败: ${e.message}`);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
testResults.success = testResults.errors.length === 0;
|
|
1212
|
+
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
testResults.errors.push(error.message);
|
|
1215
|
+
testResults.success = false;
|
|
1216
|
+
|
|
1217
|
+
// 错误时也截图
|
|
1218
|
+
if (options.screenshot !== false) {
|
|
1219
|
+
try {
|
|
1220
|
+
const screenshot = await page.screenshot({ fullPage: true });
|
|
1221
|
+
testResults.screenshots.push(screenshot.toString('base64'));
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
// 忽略截图错误
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
} finally {
|
|
1227
|
+
await browser.close();
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// 生成测试报告
|
|
1231
|
+
const stepsText = testResults.steps.map(s => {
|
|
1232
|
+
const icon = s.status === 'success' ? '✅' : s.status === 'error' ? '❌' : s.status === 'running' ? '⏳' : 'ℹ️';
|
|
1233
|
+
return `${icon} ${s.step}: ${s.message}`;
|
|
1234
|
+
}).join('\n');
|
|
1235
|
+
|
|
1236
|
+
const networkText = testResults.networkRequests.length > 0
|
|
1237
|
+
? `\n\n**网络请求记录** (${testResults.networkRequests.length}条):\n` +
|
|
1238
|
+
testResults.networkRequests.slice(0, 10).map(r => {
|
|
1239
|
+
const statusIcon = r.success ? '✅' : r.status >= 400 ? '❌' : '⚠️';
|
|
1240
|
+
return `${statusIcon} ${r.method} ${r.url} ${r.status ? `(${r.status})` : ''}`;
|
|
1241
|
+
}).join('\n') +
|
|
1242
|
+
(testResults.networkRequests.length > 10 ? `\n... 还有 ${testResults.networkRequests.length - 10} 条请求` : '')
|
|
1243
|
+
: '';
|
|
1244
|
+
|
|
1245
|
+
const errorsText = testResults.errors.length > 0
|
|
1246
|
+
? `\n\n**错误信息**:\n${testResults.errors.map(e => `❌ ${e}`).join('\n')}`
|
|
1247
|
+
: '';
|
|
1248
|
+
|
|
1249
|
+
const resultText = `## 浏览器测试结果\n\n` +
|
|
1250
|
+
`**测试用例**: ${testCaseId} (${testPlanCollectionName})\n` +
|
|
1251
|
+
`**应用地址**: ${appUrl}\n` +
|
|
1252
|
+
`**测试状态**: ${testResults.success ? '✅ 成功' : '❌ 失败'}\n\n` +
|
|
1253
|
+
`**测试步骤**:\n${stepsText}` +
|
|
1254
|
+
networkText +
|
|
1255
|
+
errorsText +
|
|
1256
|
+
(testResults.screenshots.length > 0 ? `\n\n**截图**: 已保存 ${testResults.screenshots.length} 张截图` : '');
|
|
1257
|
+
|
|
1258
|
+
return {
|
|
1259
|
+
content: [
|
|
1260
|
+
{
|
|
1261
|
+
type: 'text',
|
|
1262
|
+
text: resultText
|
|
1263
|
+
}
|
|
1264
|
+
]
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
return {
|
|
1269
|
+
content: [
|
|
1270
|
+
{
|
|
1271
|
+
type: 'text',
|
|
1272
|
+
text: `❌ 浏览器测试执行失败:${error.message}\n${error.stack || ''}`
|
|
1273
|
+
}
|
|
1274
|
+
],
|
|
1275
|
+
isError: true
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
837
1280
|
async run() {
|
|
838
1281
|
const transport = new StdioServerTransport();
|
|
839
1282
|
await this.server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cqsjjb/meter-sphere-mcp-server",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "MCP server for MeterSphere test cases platform - Get test cases, generate AI test prompts,
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "MCP server for MeterSphere test cases platform - Get test cases, generate AI test prompts, track testing progress, and execute browser automation tests",
|
|
5
5
|
"main": "mcp-server.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -17,13 +17,19 @@
|
|
|
17
17
|
"test-cases",
|
|
18
18
|
"testing",
|
|
19
19
|
"ai",
|
|
20
|
-
"cursor"
|
|
20
|
+
"cursor",
|
|
21
|
+
"browser-testing",
|
|
22
|
+
"playwright",
|
|
23
|
+
"automation"
|
|
21
24
|
],
|
|
22
25
|
"author": "",
|
|
23
26
|
"license": "ISC",
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"@modelcontextprotocol/sdk": "^0.5.0"
|
|
26
29
|
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"playwright": "^1.40.0"
|
|
32
|
+
},
|
|
27
33
|
"engines": {
|
|
28
34
|
"node": ">=18.0.0"
|
|
29
35
|
},
|