integration-test-mcp
Version:
MCP服务器用于集成测试
402 lines (346 loc) • 11.1 kB
text/typescript
/**
* 集成测试 MCP 服务器工具函数
*/
import os from 'os';
import axios, { AxiosInstance } from 'axios';
import puppeteer from 'puppeteer-core';
// 全局配置类型定义
interface Config {
loginUrl: string;
api: {
baseUrl: string;
featureList: string;
submitTest: string;
pipelineBranches: string;
pipelineIntegrate: string;
headers: {
'content-type': string;
'group-id': string;
[key: string]: string;
};
pipeline: {
taskId: string;
pipelineId: string;
stageId: string;
};
};
credentials: {
selectors: {
usernameInput: string;
passwordInput: string;
submitButton: string;
};
};
cookieFields: string[];
loginSuccessCondition: {
urlIncludes?: string;
elementExists?: string;
};
}
// 响应类型定义
export interface ApiResponse<T = any> {
code: number;
msg: string;
data: T;
}
export interface FeatureItem {
id: string;
name: string;
branch_name: string;
status: string;
[key: string]: any;
}
export interface FeatureListResponse {
items: FeatureItem[];
total: number;
[key: string]: any;
}
export interface PipelineBranch {
id: string;
name: string;
status: string;
[key: string]: any;
}
export interface PipelineBranchesResponse {
items: PipelineBranch[];
total: number;
[key: string]: any;
}
export interface IntegrationTestResult {
success: boolean;
branch?: string;
feature?: {
id: string;
name: string;
branch_name: string;
status: string;
};
submit?: {
code: number;
msg: string;
success: any;
};
pipeline?: {
id: string;
name: string;
status: string;
};
integrate?: {
code: number;
msg: string;
success: any;
};
error?: string;
}
// 全局配置
export const CONFIG: Config = {
// 登录页面URL
loginUrl: process.env.MCP_API_BASE_URL || '',
// API配置
api: {
baseUrl: process.env.MCP_API_BASE_URL || '',
featureList: "/brapi/feature/list",
submitTest: "/brapi/feature/submit_test",
pipelineBranches: "/plapi/branch/get_feature_list",
pipelineIntegrate: "/plapi/stage/integrate",
headers: {
"content-type": "application/json",
"group-id": "3a079c60-d2ec-9e36-0135-6112ed2fe6ed",
},
pipeline: {
taskId: "3a1277fd-8c79-6c90-b9a7-ae75a80b472a",
pipelineId: "3a1277f8-a6c1-1c75-7428-4ecd9f1617e7",
stageId: "3a1277fd-8c5f-3fca-12f1-81799faf345d",
},
},
// 登录凭证
credentials: {
selectors: {
usernameInput: ".login-user",
passwordInput: ".login-pwd",
submitButton: ".login-button",
},
},
// 需要获取的cookie字段
cookieFields: ["token"],
// 登录成功后的判断条件
loginSuccessCondition: {
urlIncludes: "/feature",
elementExists: ".ant-avatar",
},
};
/**
* 获取登录凭证
* @returns 返回用户名和密码
* @throws 如果环境变量未设置则抛出错误
*/
export function getCredentials(): { username: string; password: string } {
const username = process.env.MCP_USERNAME;
const password = process.env.MCP_PASSWORD;
const baseUrl = process.env.MCP_API_BASE_URL;
if (!username || !password) {
throw new Error('环境变量 MCP_USERNAME 或 MCP_PASSWORD 未设置,请先设置环境变量');
}
if (!baseUrl) {
throw new Error('环境变量 MCP_API_BASE_URL 未设置,请先设置环境变量');
}
return { username, password };
}
export function createApiClient(token: string): AxiosInstance {
const client = axios.create({
baseURL: CONFIG.api.baseUrl,
headers: {
...CONFIG.api.headers,
Cookie: `token=${token}`,
},
});
client.interceptors.response.use(
(response) => response.data,
(error) => {
throw new Error(
`API请求失败: ${error.response?.data?.msg || error.message}`
);
}
);
return client;
}
export async function getFeatureList(apiClient: AxiosInstance, branchName: string): Promise<ApiResponse<FeatureListResponse>> {
const params = {
page_size: 100,
page: 1,
show_myself: 1,
name: branchName,
};
const response = await apiClient.get<any, ApiResponse<FeatureListResponse>>(CONFIG.api.featureList, { params });
if (response.code !== 0) {
throw new Error(response.msg || "获取特性列表失败");
}
if (!response.data.items?.length) {
throw new Error(`未找到分支 ${branchName} 对应的特性数据`);
}
return response;
}
export async function submitFeatureToTest(apiClient: AxiosInstance, featureId: string): Promise<ApiResponse<any>> {
const data = {
id: featureId,
action: "submit_test",
info: "1",
};
const response = await apiClient.post<any, ApiResponse<any>>(CONFIG.api.submitTest, data);
if (response.code === 10000) {
throw new Error(response.msg || "提测失败");
}
if (response.code !== 0) {
throw new Error(response.msg || "提交特性到测试失败");
}
return response;
}
export async function getPipelineBranches(apiClient: AxiosInstance, branchName: string): Promise<ApiResponse<PipelineBranchesResponse>> {
const params = {
task_id: CONFIG.api.pipeline.taskId,
pipeline_id: CONFIG.api.pipeline.pipelineId,
page: 1,
page_size: 10,
name: branchName,
};
const response = await apiClient.get<any, ApiResponse<PipelineBranchesResponse>>(CONFIG.api.pipelineBranches, {
params,
});
if (response.code !== 0) {
throw new Error(response.msg || "获取流水线分支列表失败");
}
if (!response.data.items?.length) {
throw new Error(`未找到分支 ${branchName} 对应的流水线数据`);
}
return response;
}
export async function integratePipeline(apiClient: AxiosInstance, featureId: string): Promise<ApiResponse<any>> {
const data = {
pipeline_id: CONFIG.api.pipeline.pipelineId,
stage_id: CONFIG.api.pipeline.stageId,
task_id: CONFIG.api.pipeline.taskId,
feature_ids: featureId,
};
const response = await apiClient.post<any, ApiResponse<any>>(CONFIG.api.pipelineIntegrate, data);
if (response.code !== 0) {
throw new Error(response.msg || "执行流水线集成失败");
}
return response;
}
export function getEdgePath(): string {
const platform = os.platform();
switch (platform) {
case "win32":
return "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
case "darwin":
return "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge";
case "linux":
return "/usr/bin/microsoft-edge";
default:
throw new Error(`不支持的操作系统: ${platform}`);
}
}
export async function loginAndGetCookies(): Promise<Record<string, string>> {
let browserInstance = null;
try {
const launchOptions = {
headless: true,
defaultViewport: {
width: 1920,
height: 1080,
},
args: [
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-sandbox",
],
executablePath: getEdgePath(),
};
// 获取登录凭证
const { username, password } = getCredentials();
browserInstance = await puppeteer.launch(launchOptions);
const page = await browserInstance.newPage();
await page.goto(CONFIG.loginUrl, { waitUntil: "networkidle2" });
await page.type(
CONFIG.credentials.selectors.usernameInput,
username
);
await page.type(
CONFIG.credentials.selectors.passwordInput,
password
);
await page.click(CONFIG.credentials.selectors.submitButton);
if (CONFIG.loginSuccessCondition.urlIncludes) {
await page.waitForFunction(
(expectedUrl: string) => window.location.href.includes(expectedUrl),
{},
CONFIG.loginSuccessCondition.urlIncludes
);
}
if (CONFIG.loginSuccessCondition.elementExists) {
await page.waitForSelector(CONFIG.loginSuccessCondition.elementExists);
}
const cookies = await page.cookies();
const result: Record<string, string> = {};
for (const cookie of cookies) {
if (CONFIG.cookieFields.includes(cookie.name)) {
result[cookie.name] = cookie.value;
}
}
return result;
} catch (error) {
throw new Error(`登录过程中发生错误: ${(error as Error).message}`);
} finally {
if (browserInstance) {
try {
await browserInstance.close();
} catch (closeError) {
console.error("关闭浏览器时发生错误:", (closeError as Error).message);
}
}
}
}
// 主要业务逻辑
export async function runIntegrationTest(branchName: string): Promise<IntegrationTestResult> {
try {
const cookies = await loginAndGetCookies();
const apiClient = createApiClient(cookies.token);
const featureList = await getFeatureList(apiClient, branchName);
const feature = featureList.data.items[0];
const submitResult = await submitFeatureToTest(apiClient, feature.id);
const pipelineBranches = await getPipelineBranches(apiClient, branchName);
const branch = pipelineBranches.data.items[0];
const integrateResult = await integratePipeline(apiClient, branch.id);
return {
success: true,
branch: branchName,
feature: {
id: feature.id,
name: feature.name,
branch_name: feature.branch_name,
status: feature.status,
},
submit: {
code: submitResult.code,
msg: submitResult.msg,
success: submitResult.data,
},
pipeline: {
id: branch.id,
name: branch.name,
status: branch.status,
},
integrate: {
code: integrateResult.code,
msg: integrateResult.msg,
success: integrateResult.data,
},
};
} catch (error) {
return {
success: false,
error: (error as Error).message,
};
}
}