UNPKG

playwright-ai-auto-debug

Version:

Automatic Playwright test debugging with AI assistance + UI Test Coverage Analysis

403 lines (345 loc) 16.5 kB
// src/application/usecases/AnalyzeTestErrorsUseCase.js import { TestError } from '../../domain/entities/TestError.js'; import { AIResponse } from '../../domain/entities/AIResponse.js'; /** * Use Case для анализа ошибок тестов * Оркестрирует весь процесс анализа от поиска ошибок до создания отчетов */ export class AnalyzeTestErrorsUseCase { constructor(errorRepository, aiProvider, reporterManager, mcpClient = null) { this.errorRepository = errorRepository; this.aiProvider = aiProvider; this.reporterManager = reporterManager; this.mcpClient = mcpClient; } /** * Выполняет анализ ошибок тестов * @param {Object} request - запрос на анализ * @param {string} request.projectPath - путь к проекту * @param {Object} request.config - конфигурация * @param {boolean} request.useMcp - использовать MCP для DOM snapshots * @returns {Promise<Object>} - результат анализа */ async execute(request) { const { projectPath, config, useMcp = false } = request; console.log('🚀 Starting test error analysis...'); console.log(`📁 Project path: ${projectPath}`); console.log(`🤖 AI Provider: ${this.aiProvider.getProviderName()}`); console.log(`🔗 MCP enabled: ${useMcp && this.mcpClient ? 'Yes' : 'No'}`); console.log(`⚙️ Configuration check:`); console.log(` 📝 save_ai_responses: ${config.save_ai_responses}`); console.log(` 📁 ai_responses_dir: ${config.ai_responses_dir}`); console.log(` 📊 allure_integration: ${config.allure_integration}`); console.log(` 📁 allure_results_dir: ${config.allure_results_dir}`); console.log(` 🌐 report_dir: ${config.report_dir}`); const results = { success: false, processed: 0, errors: 0, total: 0, analysisResults: [], summary: null, startTime: new Date(), endTime: null, processingTime: 0 }; try { // 1. Инициализация MCP клиента (если требуется) if (useMcp && this.mcpClient) { console.log('🔗 Initializing MCP client...'); const mcpStarted = await this.mcpClient.start(); if (!mcpStarted) { console.warn('⚠️ MCP client failed to start, continuing without MCP'); this.mcpClient = null; } } // 2. Поиск файлов ошибок console.log('🔍 Searching for error files...'); const errorFiles = await this.errorRepository.findErrors(projectPath, config); if (errorFiles.length === 0) { console.log('ℹ️ No error files found'); results.success = true; results.endTime = new Date(); results.processingTime = results.endTime - results.startTime; return results; } console.log(`📋 Found ${errorFiles.length} error file(s)`); results.total = errorFiles.length; // 3. Анализ каждой ошибки for (let i = 0; i < errorFiles.length; i++) { const testError = errorFiles[i]; // errorFiles уже содержит TestError объекты console.log(`\n📝 Processing ${i + 1}/${errorFiles.length}: ${testError.filePath}`); try { // TestError уже создан в repository, просто используем его console.log(`🎯 Error type: ${testError.errorType}`); console.log(`📊 Severity: ${testError.severity}`); console.log(`🔤 Keywords: ${testError.keywords.slice(0, 3).join(', ')}${testError.keywords.length > 3 ? '...' : ''}`); // Получаем DOM snapshot если MCP доступен let domSnapshot = null; if (this.mcpClient && testError.hasDomContext()) { try { console.log('📸 Getting DOM snapshot from MCP...'); domSnapshot = await this.mcpClient.getSnapshot(); console.log(`✅ DOM snapshot received: ${domSnapshot.elements?.length || 0} elements`); } catch (error) { console.warn(`⚠️ Failed to get DOM snapshot: ${error.message}`); } } // Подготавливаем промпт с учетом лимитов const maxLength = config.max_prompt_length || 2000; const truncatedContent = testError.content.length > maxLength ? testError.content.substring(0, maxLength) + '\n...(content truncated)' : testError.content; console.log(`📏 Content length: ${testError.content.length} chars${testError.content.length > maxLength ? ' (truncated)' : ''}`); // Генерируем ответ ИИ console.log('🤖 Generating AI response...'); const startTime = Date.now(); const rawResponse = await this.aiProvider.generateResponse( truncatedContent, config, domSnapshot ); const processingTime = Date.now() - startTime; console.log(`⏱️ AI response generated in ${processingTime}ms`); if (!rawResponse || rawResponse.trim().length === 0) { throw new Error('Empty response from AI provider'); } // Создаем доменную сущность ответа ИИ const aiResponse = new AIResponse(rawResponse, testError, { model: config.model, provider: this.aiProvider.getProviderName(), processingTime, timestamp: new Date() }); console.log(`📊 Response confidence: ${Math.round(aiResponse.confidence * 100)}%`); console.log(`🎬 Actions found: ${aiResponse.actions.length}`); console.log(`💡 Recommendations: ${aiResponse.recommendations.length}`); // Валидация качества ответа const qualityCheck = aiResponse.validateQuality(); if (qualityCheck.warnings.length > 0) { console.warn('⚠️ Quality warnings:', qualityCheck.warnings); } // Валидация действий через MCP (если доступен) if (this.mcpClient && aiResponse.hasExecutableCode()) { try { console.log('🧪 Validating actions through MCP...'); const validationResults = await this.validateActionsViaMcp(aiResponse); // Обогащаем ответ результатами валидации aiResponse.mcpValidation = validationResults; console.log(`✅ MCP validation: ${validationResults.successfulActions}/${validationResults.totalActions} successful`); } catch (error) { console.warn(`⚠️ MCP validation failed: ${error.message}`); } } // Создаем отчеты console.log('📄 Creating reports...'); console.log(`📝 Report data: testError=${!!testError}, aiResponse=${!!aiResponse}`); console.log(`📁 Test file: ${testError?.filePath || 'Unknown'}`); console.log(`🤖 AI response length: ${aiResponse?.content?.length || 0} chars`); await this.reporterManager.createReports([{ testError, aiResponse, errorFile: testError, timestamp: new Date(), success: true }]); results.analysisResults.push({ testError, aiResponse, errorFile: testError, success: true }); results.processed++; console.log(`✅ Successfully processed file ${i + 1}/${errorFiles.length}`); // Пауза между запросами для соблюдения rate limits if (i < errorFiles.length - 1) { const delay = config.request_delay || 1000; console.log(`⏳ Waiting ${delay}ms before next request...`); await this.sleep(delay); } } catch (error) { results.errors++; console.error(`❌ Error processing ${testError.filePath}: ${error.message}`); // Улучшенная обработка ошибок с конкретными рекомендациями this.handleProcessingError(error, testError.filePath); results.analysisResults.push({ testError: testError, aiResponse: null, errorFile: testError, success: false, error: error.message }); } } // 4. Создание общего резюме results.summary = this.createSummary(results); results.success = results.errors === 0; results.endTime = new Date(); results.processingTime = results.endTime - results.startTime; // 5. Финальный отчет console.log(`\n📊 Analysis Summary:`); console.log(` ✅ Successfully processed: ${results.processed}/${results.total}`); console.log(` ❌ Errors encountered: ${results.errors}/${results.total}`); console.log(` ⏱️ Total processing time: ${results.processingTime}ms`); if (results.summary.topErrorTypes.length > 0) { console.log(` 🎯 Top error types: ${results.summary.topErrorTypes.slice(0, 3).join(', ')}`); } console.log(`\n📄 Generated Reports:`); console.log(` 🌐 HTML Report: ${config.report_dir || 'playwright-report'}/ai-analysis-*.html`); console.log(` 📝 Markdown Reports: ${config.ai_responses_dir || 'ai-responses'}/`); if (config.allure_integration) { console.log(` 📊 Allure Attachments: ${config.allure_results_dir || 'allure-results'}/`); } // Проверяем фактическое наличие созданных файлов await this.verifyGeneratedReports(config); return results; } catch (error) { console.error('❌ Critical error during analysis:', error.message); results.success = false; results.endTime = new Date(); results.processingTime = results.endTime - results.startTime; throw error; } finally { // Cleanup MCP client if (this.mcpClient) { await this.mcpClient.cleanup(); } } } /** * Валидирует действия через MCP * @param {AIResponse} aiResponse - ответ ИИ с действиями * @returns {Promise<Object>} - результаты валидации */ async validateActionsViaMcp(aiResponse) { const executableCode = aiResponse.getExecutableCode(); const actions = []; // Конвертируем код в MCP действия (упрощенная версия) for (const snippet of executableCode) { const clickMatches = snippet.code.match(/\.click\(\)/g) || []; const fillMatches = snippet.code.match(/\.fill\(['"`]([^'"`]+)['"`]\)/g) || []; actions.push(...clickMatches.map(() => ({ type: 'click', ref: 'element_1' }))); actions.push(...fillMatches.map(match => { const value = match.match(/['"`]([^'"`]+)['"`]/)?.[1] || ''; return { type: 'fill', ref: 'element_1', value }; })); } if (actions.length === 0) { return { totalActions: 0, successfulActions: 0, results: [] }; } return await this.mcpClient.validateActions(actions); } /** * Обрабатывает ошибки с конкретными рекомендациями * @param {Error} error - ошибка * @param {string} filePath - путь к файлу */ handleProcessingError(error, filePath) { if (error.message.includes('429')) { console.error('💡 Recommendation: Increase request_delay in configuration'); } else if (error.message.includes('401') || error.message.includes('403')) { console.error('💡 Recommendation: Check your API key configuration'); } else if (error.message.includes('timeout')) { console.error('💡 Recommendation: Check network connection or increase timeout'); } } /** * Создает резюме результатов анализа * @param {Object} results - результаты анализа * @returns {Object} - резюме */ createSummary(results) { const successful = results.analysisResults.filter(r => r.success); const errorTypes = {}; const severities = {}; let totalConfidence = 0; let totalActions = 0; let totalRecommendations = 0; for (const result of successful) { if (result.testError) { errorTypes[result.testError.errorType] = (errorTypes[result.testError.errorType] || 0) + 1; severities[result.testError.severity] = (severities[result.testError.severity] || 0) + 1; } if (result.aiResponse) { totalConfidence += result.aiResponse.confidence; totalActions += result.aiResponse.actions.length; totalRecommendations += result.aiResponse.recommendations.length; } } return { totalFiles: results.total, processedFiles: results.processed, errorFiles: results.errors, successRate: results.total > 0 ? (results.processed / results.total) * 100 : 0, averageConfidence: successful.length > 0 ? (totalConfidence / successful.length) * 100 : 0, totalActions, totalRecommendations, topErrorTypes: Object.entries(errorTypes) .sort(([,a], [,b]) => b - a) .map(([type]) => type), topSeverities: Object.entries(severities) .sort(([,a], [,b]) => b - a) .map(([severity]) => severity), processingTimeMs: results.processingTime }; } /** * Проверяет наличие созданных отчетов * @param {Object} config - конфигурация */ async verifyGeneratedReports(config) { const fs = await import('fs'); const path = await import('path'); const { glob } = await import('glob'); console.log(`\n🔍 Проверка созданных файлов отчетов:`); try { // Проверяем HTML отчеты const reportDir = config.report_dir || 'playwright-report'; if (fs.existsSync(reportDir)) { const htmlFiles = await glob(path.join(reportDir, 'ai-analysis-*.html')); if (htmlFiles.length > 0) { console.log(` ✅ HTML отчет: ${htmlFiles[htmlFiles.length - 1]}`); } else { console.log(` ❌ HTML отчет не найден в ${reportDir}`); } } else { console.log(` ❌ Директория HTML отчетов не найдена: ${reportDir}`); } // Проверяем Markdown отчеты const aiResponsesDir = config.ai_responses_dir || 'ai-responses'; if (fs.existsSync(aiResponsesDir)) { const markdownFiles = await glob(path.join(aiResponsesDir, '*.md')); console.log(` ✅ Markdown файлов: ${markdownFiles.length} в ${aiResponsesDir}`); // Показываем последние файлы if (markdownFiles.length > 0) { const recentFiles = markdownFiles.slice(-3); recentFiles.forEach(file => { console.log(` 📄 ${path.basename(file)}`); }); } } else { console.log(` ❌ Директория Markdown отчетов не найдена: ${aiResponsesDir}`); } // Проверяем Allure attachments if (config.allure_integration) { const allureDir = config.allure_results_dir || 'allure-results'; if (fs.existsSync(allureDir)) { const allureFiles = await glob(path.join(allureDir, 'ai-analysis-*.md')); console.log(` ✅ Allure attachments: ${allureFiles.length} в ${allureDir}`); } else { console.log(` ❌ Директория Allure не найдена: ${allureDir}`); } } } catch (error) { console.warn(`⚠️ Ошибка при проверке отчетов: ${error.message}`); } } /** * Утилита для паузы * @param {number} ms - миллисекунды * @returns {Promise<void>} */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } }