Skip to main content
Glama
autospectra-bridge.ts12.4 kB
/** * AutoSpectra Integration Bridge * Connects TestRail MCP Server with AutoSpectra Framework */ import { TestRailMCPTools } from '../tools/testrail-tools'; export interface AutoSpectraTestResult { testId: string; title: string; status: 'passed' | 'failed' | 'skipped' | 'blocked'; duration?: number; error?: string; screenshots?: string[]; logs?: string[]; metadata?: { framework?: string; browser?: string; environment?: string; buildNumber?: string; branch?: string; commit?: string; }; } export interface AutoSpectraTestSuite { suiteId: string; name: string; results: AutoSpectraTestResult[]; summary: { total: number; passed: number; failed: number; skipped: number; blocked: number; duration: number; }; } export class AutoSpectraBridge { private testRailTools: TestRailMCPTools; private isConnected: boolean = false; private runMapping: Map<string, number> = new Map(); // AutoSpectra suite ID -> TestRail run ID constructor() { this.testRailTools = new TestRailMCPTools(); } /** * Initialize connection to TestRail */ async connect(config: { baseUrl: string; username: string; apiKey: string; projectId: number; }): Promise<boolean> { try { await this.testRailTools.connectTestRail({ baseUrl: config.baseUrl, username: config.username, apiKey: config.apiKey, }); this.isConnected = true; return true; } catch (error) { this.isConnected = false; return false; } } /** * Create TestRail run from AutoSpectra test suite */ async createRunFromSuite( projectId: number, suite: AutoSpectraTestSuite, options?: { milestoneId?: number; assignedToId?: number; environment?: string; buildNumber?: string; } ): Promise<number | null> { if (!this.isConnected) { throw new Error('TestRail connection not established'); } try { const runName = `AutoSpectra - ${suite.name} - ${new Date().toISOString().split('T')[0]}`; const description = this.buildRunDescription(suite, options); const result = await this.testRailTools.createRun({ projectId, name: runName, description, ...(options?.milestoneId && { milestoneId: options.milestoneId }), ...(options?.assignedToId && { assignedToId: options.assignedToId }), includeAll: true, // Include all cases for now }); if (result.content && result.content[0]) { const response = JSON.parse(result.content[0].text as string); if (response.success && response.data.run) { const runId = response.data.run.id; this.runMapping.set(suite.suiteId, runId); return runId; } } return null; } catch (error) { return null; } } /** * Submit AutoSpectra test results to TestRail */ async submitResults( suite: AutoSpectraTestSuite, caseMapping: Map<string, number>, // AutoSpectra test ID -> TestRail case ID options?: { closeRun?: boolean; addDefects?: boolean; } ): Promise<boolean> { if (!this.isConnected) { throw new Error('TestRail connection not established'); } const runId = this.runMapping.get(suite.suiteId); if (!runId) { throw new Error(`No TestRail run found for suite ${suite.suiteId}`); } try { const results = suite.results .filter((result) => caseMapping.has(result.testId)) .map((result) => ({ caseId: caseMapping.get(result.testId)!, statusId: this.mapAutoSpectraStatus(result.status), comment: this.buildResultComment(result), elapsed: result.duration ? this.formatDuration(result.duration) : undefined, version: result.metadata?.buildNumber, defects: result.status === 'failed' && options?.addDefects ? `AUTO-${result.testId}-${Date.now()}` : undefined, })); if (results.length === 0) { return true; // No results to submit } await this.testRailTools.addBulkResults({ runId, results: results.map((result) => ({ caseId: result.caseId, statusId: result.statusId, comment: result.comment, ...(result.elapsed && { elapsed: result.elapsed }), ...(result.version && { version: result.version }), ...(result.defects && { defects: result.defects }), })), }); // Close run if requested if (options?.closeRun) { await this.testRailTools.closeRun({ runId }); } return true; } catch (error) { return false; } } /** * Auto-sync AutoSpectra execution with TestRail */ async autoSync( projectId: number, suite: AutoSpectraTestSuite, options?: { createCasesIfMissing?: boolean; milestoneId?: number; environment?: string; buildNumber?: string; } ): Promise<{ success: boolean; runId?: number; createdCases?: number; submittedResults?: number; errors?: string[]; }> { const result: { success: boolean; runId?: number; createdCases?: number; submittedResults?: number; errors?: string[]; } = { success: false, createdCases: 0, submittedResults: 0, errors: [] as string[], }; try { // Step 1: Get or create TestRail cases const caseMapping = new Map<string, number>(); if (options?.createCasesIfMissing) { // Auto-create missing test cases for (const testResult of suite.results) { const caseId = await this.getOrCreateCase(projectId, testResult); if (caseId) { caseMapping.set(testResult.testId, caseId); if (caseId > 0) result.createdCases = (result.createdCases || 0) + 1; } } } // Step 2: Create test run const runId = await this.createRunFromSuite(projectId, suite, { ...(options?.milestoneId && { milestoneId: options.milestoneId }), ...(options?.environment && { environment: options.environment }), ...(options?.buildNumber && { buildNumber: options.buildNumber }), }); if (!runId) { (result.errors || []).push('Failed to create TestRail run'); return result; } result.runId = runId; // Step 3: Submit results const submitSuccess = await this.submitResults(suite, caseMapping, { closeRun: true, addDefects: true, }); if (submitSuccess) { result.submittedResults = suite.results.length; result.success = true; } else { (result.errors || []).push('Failed to submit test results'); } return result; } catch (error) { (result.errors || []).push(error instanceof Error ? error.message : 'Unknown error'); return result; } } /** * Generate TestRail dashboard for AutoSpectra project */ async generateDashboard( projectId: number, timeRange?: { start: string; end: string } ): Promise<any> { if (!this.isConnected) { throw new Error('TestRail connection not established'); } try { const result = await this.testRailTools.generateProjectDashboard({ projectId, timeRange, includeMetrics: true, includeTrends: true, includeTopFailures: true, }); if (result.content && result.content[0]) { const response = JSON.parse(result.content[0].text as string); if (response.success) { return response.data; } } return null; } catch (error) { return null; } } // Helper methods private buildRunDescription(suite: AutoSpectraTestSuite, options?: any): string { let description = `AutoSpectra Test Execution - ${suite.name}\n\n`; description += `Summary:\n`; description += `- Total Tests: ${suite.summary.total}\n`; description += `- Passed: ${suite.summary.passed}\n`; description += `- Failed: ${suite.summary.failed}\n`; description += `- Skipped: ${suite.summary.skipped}\n`; description += `- Duration: ${this.formatDuration(suite.summary.duration)}\n\n`; if (options?.environment) { description += `Environment: ${options.environment}\n`; } if (options?.buildNumber) { description += `Build: ${options.buildNumber}\n`; } description += `Generated by AutoSpectra Bridge at ${new Date().toISOString()}`; return description; } private mapAutoSpectraStatus(status: string): number { switch (status.toLowerCase()) { case 'passed': return 1; case 'failed': return 5; case 'skipped': return 2; case 'blocked': return 2; default: return 3; // Untested } } private buildResultComment(result: AutoSpectraTestResult): string { let comment = `AutoSpectra Test Result: ${result.status.toUpperCase()}\n\n`; if (result.error) { comment += `Error: ${result.error}\n\n`; } if (result.metadata) { comment += `Execution Details:\n`; if (result.metadata.framework) comment += `- Framework: ${result.metadata.framework}\n`; if (result.metadata.browser) comment += `- Browser: ${result.metadata.browser}\n`; if (result.metadata.environment) comment += `- Environment: ${result.metadata.environment}\n`; if (result.metadata.buildNumber) comment += `- Build: ${result.metadata.buildNumber}\n`; if (result.metadata.branch) comment += `- Branch: ${result.metadata.branch}\n`; if (result.metadata.commit) comment += `- Commit: ${result.metadata.commit}\n`; } if (result.screenshots && result.screenshots.length > 0) { comment += `\nScreenshots: ${result.screenshots.length} captured`; } if (result.logs && result.logs.length > 0) { comment += `\nLogs: ${result.logs.length} files available`; } return comment; } private formatDuration(milliseconds: number): string { if (milliseconds < 1000) return `${milliseconds}ms`; if (milliseconds < 60000) return `${(milliseconds / 1000).toFixed(1)}s`; const minutes = Math.floor(milliseconds / 60000); const seconds = Math.floor((milliseconds % 60000) / 1000); return `${minutes}m ${seconds}s`; } private async getOrCreateCase( projectId: number, testResult: AutoSpectraTestResult ): Promise<number | null> { try { // First, try to find existing case by title const existingCases = await this.testRailTools.getCases({ projectId, limit: 1000, }); if (existingCases.content && existingCases.content[0]) { const response = JSON.parse(existingCases.content[0].text as string); if (response.success && response.data.cases) { const existingCase = response.data.cases.find( (c: any) => c.title.toLowerCase() === testResult.title.toLowerCase() ); if (existingCase) { return existingCase.id; } } } // Create new case if not found const createResult = await this.testRailTools.createCase({ sectionId: 1, // Default section - would need to be configured title: testResult.title, typeId: 6, // Functional test priorityId: 2, // Medium priority refs: testResult.testId, steps: `Automated test: ${testResult.testId}`, expectedResult: 'Test should pass without errors', customFields: { custom_automation_type: 'Automated', custom_test_framework: testResult.metadata?.framework || 'AutoSpectra', }, }); if (createResult.content && createResult.content[0]) { const response = JSON.parse(createResult.content[0].text as string); if (response.success && response.data.case) { return response.data.case.id; } } return null; } catch (error) { return null; } } /** * Get run mapping for debugging */ getRunMapping(): Map<string, number> { return this.runMapping; } /** * Clear run mapping */ clearRunMapping(): void { this.runMapping.clear(); } /** * Check connection status */ isConnectedToTestRail(): boolean { return this.isConnected; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samuelvinay91/testrail-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server