@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.
Files changed (3) hide show
  1. package/README.md +4 -21
  2. package/mcp-server.mjs +451 -8
  3. 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
- **Windows 路径示例:**
23
+ **使用 npx(推荐,无需本地安装):**
24
24
  ```json
25
25
  {
26
26
  "mcpServers": {
27
27
  "meter-sphere": {
28
- "command": "node",
29
- "args": ["./node_modules/@cqsjjb/meter-sphere-mcp-server/mcp-server.mjs"],
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
- **macOS/Linux 路径示例:**
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: '根据测试用例ID获取详细信息,包括测试步骤和AI测试提示语。会自动调用AI模型进行测试用例分析。配置信息从环境变量读取(PLATFORM_URL、X_AUTH_TOKEN、CSRF_TOKEN、MODEL_BASE_URL、MODEL_API_KEY、MODEL_ID)。',
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.1",
4
- "description": "MCP server for MeterSphere test cases platform - Get test cases, generate AI test prompts, and track testing progress",
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
  },