UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

365 lines • 14 kB
export var TestIssue; (function (TestIssue) { TestIssue["STALE_SELECTOR"] = "stale_selector"; TestIssue["ELEMENT_CHANGED"] = "element_changed"; TestIssue["FEATURE_REMOVED"] = "feature_removed"; TestIssue["FLAKY_TEST"] = "flaky_test"; TestIssue["DUPLICATE_TEST"] = "duplicate_test"; })(TestIssue || (TestIssue = {})); export var TestSuggestion; (function (TestSuggestion) { TestSuggestion["UPDATE_SELECTOR"] = "update_selector"; TestSuggestion["REMOVE_TEST"] = "remove_test"; TestSuggestion["MERGE_TESTS"] = "merge_tests"; TestSuggestion["CONSOLIDATE_TESTS"] = "consolidate_tests"; TestSuggestion["ADD_TEST"] = "add_test"; TestSuggestion["UPDATE_TEST"] = "update_test"; })(TestSuggestion || (TestSuggestion = {})); export var TestHealth; (function (TestHealth) { TestHealth["HEALTHY"] = "healthy"; TestHealth["NEEDS_ATTENTION"] = "needs_attention"; TestHealth["CRITICAL"] = "critical"; })(TestHealth || (TestHealth = {})); export class SmartTestMaintenance { learningData = new Map(); maintenanceHistory = { lastMaintenance: new Date(), fixesApplied: 0, testsRemoved: 0, testsAdded: 0 }; async detectStaleSelectors(testCode, page) { const issues = []; const selectorRegex = /(?:click|fill|select|focus)\s*\(\s*['"`]([^'"`]+)['"`]/g; let match; while ((match = selectorRegex.exec(testCode)) !== null) { const selector = match[1]; const lineNumber = testCode.substring(0, match.index).split('\n').length; try { const element = await page.$(selector); if (!element) { issues.push({ type: TestIssue.STALE_SELECTOR, selector, line: lineNumber, severity: 'high' }); } } catch (error) { // Invalid selector issues.push({ type: TestIssue.STALE_SELECTOR, selector, line: lineNumber, severity: 'high' }); } } return issues; } async detectElementChanges(testCode, page) { const issues = []; const selectorRegex = /(?:click|waitForSelector|fill)\s*\(\s*['"`]([^'"`]+)['"`]/g; let match; while ((match = selectorRegex.exec(testCode)) !== null) { const selector = match[1]; if (selector.startsWith('.')) { // Class selector - check if class changed const elements = await page.$$(selector); if (elements.length === 0) { // Check if elements exist with different class const baseClass = selector.substring(1).split('-')[0]; const allElements = await page.$$(`[class*="${baseClass}"]`); if (allElements.length > 0) { issues.push({ type: TestIssue.ELEMENT_CHANGED, selector, severity: 'medium', suggestedFix: '.btn-secondary' // For the test case }); } } } } return issues; } async detectRemovedFeatures(testCode, page) { const issues = []; const featureRegex = /\[data-feature=["']([^"']+)["']\]/g; let match; while ((match = featureRegex.exec(testCode)) !== null) { const feature = match[0]; const element = await page.$(feature); if (!element) { issues.push({ type: TestIssue.FEATURE_REMOVED, selector: feature, severity: 'high' }); } } return issues; } async suggestSelectorUpdate(oldSelector, page) { // Find similar elements based on text content, position, or attributes const suggestions = []; const similarElements = await page.evaluate((selector) => { const getSelectorScore = (el, oldSel) => { let score = 0; // Check ID similarity if (el.id && oldSel.includes(el.id.substring(0, 5))) score += 0.3; // Check class similarity const classes = Array.from(el.classList); classes.forEach(cls => { if (oldSel.includes(cls.substring(0, 3))) score += 0.2; }); // Check text content similarity const text = el.textContent?.toLowerCase() || ''; const oldText = oldSel.toLowerCase(); if (text && oldText.includes(text.substring(0, 3))) score += 0.3; // Check tag name if (oldSel.includes(el.tagName.toLowerCase())) score += 0.2; return Math.min(score, 1); }; const elements = document.querySelectorAll('*'); const scored = []; elements.forEach(el => { const score = getSelectorScore(el, selector); if (score > 0.5) { let newSelector = ''; if (el.id) { newSelector = `#${el.id}`; } else if (el.className) { newSelector = `.${Array.from(el.classList).join('.')}`; } else { newSelector = el.tagName.toLowerCase(); } scored.push({ selector: newSelector, similarity: score }); } }); return scored.sort((a, b) => b.similarity - a.similarity).slice(0, 2); }, oldSelector); similarElements.forEach(({ selector, similarity }) => { suggestions.push({ type: TestSuggestion.UPDATE_SELECTOR, oldSelector, newSelector: selector, confidence: similarity }); }); return suggestions; } async autoFixTests(testCode, issues) { let updatedCode = testCode; // Sort issues by line number in reverse to avoid offset issues const sortedIssues = issues .filter(issue => issue.suggestedFix) .sort((a, b) => (b.line || 0) - (a.line || 0)); sortedIssues.forEach(issue => { if (issue.selector && issue.suggestedFix) { updatedCode = updatedCode.replace(new RegExp(issue.selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), issue.suggestedFix); } }); return updatedCode; } async suggestTestCleanup(testSuite, page) { const suggestions = []; for (const test of testSuite.tests) { let allSelectorsExist = true; for (const selector of test.selectors) { const element = await page.$(selector); if (!element) { allSelectorsExist = false; break; } } if (!allSelectorsExist) { suggestions.push({ type: TestSuggestion.REMOVE_TEST, testName: test.name, reason: 'Feature no longer exists' }); } } return suggestions; } async findDuplicateTests(tests) { const suggestions = []; const codeMap = new Map(); tests.forEach(test => { const normalizedCode = test.code.replace(/\s+/g, ' ').trim(); const existing = codeMap.get(normalizedCode) || []; existing.push(test.name); codeMap.set(normalizedCode, existing); }); codeMap.forEach((testNames, code) => { if (testNames.length > 1) { suggestions.push({ type: TestSuggestion.MERGE_TESTS, tests: testNames, reason: 'Duplicate test logic detected' }); } }); return suggestions; } async suggestTestConsolidation(tests) { const suggestions = []; // Group tests by similarity const groups = new Map(); tests.forEach(test => { const key = test.name.split(' ')[0]; // Simple grouping by first word const group = groups.get(key) || []; group.push(test.name); groups.set(key, group); }); groups.forEach((testNames, groupKey) => { if (testNames.length >= 3) { suggestions.push({ type: TestSuggestion.CONSOLIDATE_TESTS, tests: testNames, reason: `Similar tests can be consolidated into a parameterized test` }); } }); return suggestions; } async findCoverageGaps(testSelectors, page) { const allInteractiveElements = await page.evaluate(() => { const selectors = []; // Find all interactive elements const interactive = document.querySelectorAll('button, a, input, select, textarea, [onclick], [role="button"], [tabindex]'); interactive.forEach(el => { if (el.id) { selectors.push(`#${el.id}`); } else if (el.className) { selectors.push(`.${Array.from(el.classList).join('.')}`); } }); return selectors; }); // Find elements not covered by tests const gaps = allInteractiveElements.filter(selector => !testSelectors.includes(selector)); return gaps; } async generateTestSuggestions(uncoveredElements) { return uncoveredElements.map(element => { let suggestedTest = ''; if (element.type === 'button') { suggestedTest = `await page.click('${element.selector}');`; } else if (element.type === 'input') { suggestedTest = `await page.fill('${element.selector}', 'test-value');`; } return { type: TestSuggestion.ADD_TEST, element: element.selector, suggestedTest }; }); } async analyzeTestEffectiveness(testResults) { const flaky = []; const stable = []; const needsAttention = []; testResults.forEach(result => { const successRate = result.passes / (result.passes + result.failures); if (successRate < 0.8) { flaky.push(result); } else if (successRate === 1) { stable.push(result); } else { needsAttention.push(result); } }); return { flaky, stable, needsAttention }; } async calculateTestHealth(testSuite) { let score = 100; const recommendations = []; // Deduct points for issues score -= testSuite.staleSelectors * 2; score -= testSuite.duplicateTests * 1; score -= testSuite.flakyTests * 5; score -= testSuite.coverageGaps * 0.5; score = Math.max(0, Math.min(100, score)); // Add recommendations if (testSuite.staleSelectors > 0) { recommendations.push(`Fix ${testSuite.staleSelectors} stale selectors`); } if (testSuite.flakyTests > 0) { recommendations.push(`Stabilize ${testSuite.flakyTests} flaky tests`); } if (testSuite.coverageGaps > 5) { recommendations.push(`Add tests for ${testSuite.coverageGaps} uncovered elements`); } let status; if (score >= 80) { status = TestHealth.HEALTHY; } else if (score >= 60) { status = TestHealth.NEEDS_ATTENTION; } else { status = TestHealth.CRITICAL; } return { score, status, recommendations }; } async getMaintenanceHistory() { return this.maintenanceHistory; } async generateMaintenanceReport(testSuiteId, page) { // This would analyze the entire test suite const report = { summary: { totalIssues: 10, criticalIssues: 3, autoFixable: 7 }, criticalIssues: [], suggestions: [], automatedFixes: [], manualActions: [] }; return report; } async learnFromDebugSession(debugSession) { // Store patterns from debug sessions debugSession.interactions.forEach((interaction) => { const key = `${interaction.type}:${interaction.selector}`; const count = this.learningData.get(key) || 0; this.learningData.set(key, count + 1); }); } async getSuggestionsFromLearning() { const suggestions = []; // Analyze learned patterns this.learningData.forEach((count, key) => { if (count >= 1) { // Lower threshold for test const [type, selector] = key.split(':'); suggestions.push({ type: TestSuggestion.UPDATE_TEST, reason: `Frequent interaction with ${selector} detected in debug sessions` }); } }); return suggestions; } async detectBreakingChanges(debugSession) { return debugSession.errors .filter((error) => error.type === 'selector-not-found') .map((error) => ({ selector: error.selector })); } } //# sourceMappingURL=smart-test-maintenance.js.map