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
897 lines (854 loc) • 38.1 kB
JavaScript
/**
* Test Debugging Helper Handler
*
* Provides specialized tools for debugging test failures, particularly for:
* - Registration flow redirects
* - LiveView navigation tracking
* - Form submission verification
* - Visual regression timing
*
* Note: These tools require an existing debug session to be created first
* using inject_debugging tool.
*/
import { BaseToolHandler } from './base-handler.js';
export class TestDebuggingHelperHandler extends BaseToolHandler {
tools = [
{
name: 'verify_registration_flow',
description: `🔍 VERIFY REGISTRATION FLOW: Debug registration redirects by capturing the actual user flow, monitoring navigation events, and verifying the final destination.
Perfect for fixing test expectations when redirects don't match assumptions.
REQUIRES: Active debug session from inject_debugging tool.`,
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID from inject_debugging'
},
registrationData: {
type: 'object',
properties: {
email: { type: 'string' },
password: { type: 'string' },
name: { type: 'string' },
additionalFields: { type: 'object' }
},
required: ['email', 'password']
},
expectedRedirect: {
type: 'string',
description: 'Expected redirect URL pattern (regex supported)'
},
captureScreenshots: {
type: 'boolean',
default: true,
description: 'Capture screenshots at each step'
},
timeout: {
type: 'number',
default: 10000,
description: 'Navigation timeout in milliseconds'
}
},
required: ['sessionId', 'registrationData']
}
},
{
name: 'diagnose_liveview_death',
description: `🩺 DIAGNOSE LIVEVIEW DEATH: Capture console errors, monitor GraphQL/WebSocket failures, and track disconnections when LiveView processes die.
Essential for debugging async test failures and mock coverage issues.
REQUIRES: Active debug session from inject_debugging tool.`,
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID from inject_debugging'
},
setupActions: {
type: 'array',
items: {
type: 'object',
properties: {
action: { type: 'string', enum: ['click', 'fill', 'select', 'wait'] },
selector: { type: 'string' },
value: { type: 'string' },
timeout: { type: 'number' }
}
},
description: 'Actions to perform before monitoring'
},
monitorDuration: {
type: 'number',
default: 5000,
description: 'How long to monitor for issues (ms)'
},
captureNetworkErrors: {
type: 'boolean',
default: true,
description: 'Capture failed network requests'
},
captureConsoleErrors: {
type: 'boolean',
default: true,
description: 'Capture console errors and warnings'
}
},
required: ['sessionId']
}
},
{
name: 'capture_with_visual_stability',
description: `📸 CAPTURE WITH VISUAL STABILITY: Take screenshots only after visual stability is achieved, preventing timing issues in visual regression tests.
Waits for DOM changes to stop, animations to complete, and specific elements to render.
REQUIRES: Active debug session from inject_debugging tool.`,
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID from inject_debugging'
},
stabilityThreshold: {
type: 'number',
default: 500,
description: 'Milliseconds of no DOM changes to consider stable'
},
waitConditions: {
type: 'array',
items: {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['element', 'text', 'animation', 'network', 'custom']
},
selector: { type: 'string' },
state: { type: 'string' },
text: { type: 'string' },
customScript: { type: 'string' }
}
},
description: 'Specific conditions to wait for'
},
maxWaitTime: {
type: 'number',
default: 30000,
description: 'Maximum time to wait for stability (ms)'
},
annotations: {
type: 'array',
items: {
type: 'object',
properties: {
type: { type: 'string' },
selector: { type: 'string' },
text: { type: 'string' }
}
},
description: 'Annotations to add to the screenshot'
}
},
required: ['sessionId']
}
},
{
name: 'debug_failed_test',
description: `🐛 DEBUG FAILED TEST: Comprehensive test failure analysis with console logs, network activity, DOM snapshots, and AI-powered insights.
Generates detailed reports for debugging complex test failures.
REQUIRES: Active debug session from inject_debugging tool.`,
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID from inject_debugging'
},
testName: {
type: 'string',
description: 'Name of the failing test'
},
setupScript: {
type: 'string',
description: 'Optional JavaScript to run before debugging'
},
includeAIAnalysis: {
type: 'boolean',
default: true,
description: 'Include AI-powered failure analysis'
},
captureBeforeFailure: {
type: 'boolean',
default: true,
description: 'Capture state before the failure occurs'
},
focusAreas: {
type: 'array',
items: {
type: 'string',
enum: ['graphql', 'websocket', 'navigation', 'forms', 'async', 'dom']
},
description: 'Specific areas to focus debugging on'
}
},
required: ['sessionId', 'testName']
}
}
];
async handle(toolName, args, sessions) {
// Validate session exists
const session = sessions.get(args.sessionId);
if (!session) {
return {
content: [{
type: 'text',
text: `❌ No active debug session found with ID: ${args.sessionId}
Please first create a debug session using:
\`inject_debugging --url <your-app-url>\`
Then use the returned sessionId with this tool.`
}]
};
}
switch (toolName) {
case 'verify_registration_flow':
return this.verifyRegistrationFlow(args, session);
case 'diagnose_liveview_death':
return this.diagnoseLiveViewDeath(args, session);
case 'capture_with_visual_stability':
return this.captureWithVisualStability(args, session);
case 'debug_failed_test':
return this.debugFailedTest(args, session);
default:
throw new Error(`Unknown test debugging tool: ${toolName}`);
}
}
async verifyRegistrationFlow(args, session) {
const { registrationData, expectedRedirect, captureScreenshots = true, timeout = 10000 } = args;
try {
const page = session.page;
if (!page) {
throw new Error('No page available in session');
}
const navigationEvents = [];
const screenshots = [];
// Monitor navigation events
const navigationHandler = (frame) => {
if (frame === page.mainFrame()) {
navigationEvents.push({
type: 'navigation',
url: frame.url(),
timestamp: Date.now()
});
}
};
page.on('framenavigated', navigationHandler);
// Capture initial state
if (captureScreenshots) {
const initialScreenshot = await page.screenshot({ fullPage: true });
screenshots.push({
step: 'initial',
url: page.url(),
screenshot: initialScreenshot.toString('base64')
});
}
// Fill registration form
for (const [field, value] of Object.entries(registrationData)) {
if (field === 'additionalFields')
continue;
const selectors = [
`input[name="${field}"]`,
`input[id*="${field}"]`,
`input[placeholder*="${field}" i]`
];
let filled = false;
for (const selector of selectors) {
try {
await page.fill(selector, value);
filled = true;
break;
}
catch (e) {
// Try next selector
}
}
if (!filled) {
throw new Error(`Could not find input field for ${field}`);
}
}
// Handle additional fields
if (registrationData.additionalFields) {
for (const [field, value] of Object.entries(registrationData.additionalFields)) {
await page.fill(`input[name="${field}"]`, value);
}
}
// Capture filled form
if (captureScreenshots) {
const filledScreenshot = await page.screenshot({ fullPage: true });
screenshots.push({
step: 'form_filled',
url: page.url(),
screenshot: filledScreenshot.toString('base64')
});
}
// Submit form
const submitSelectors = [
'button[type="submit"]',
'input[type="submit"]',
'button:has-text("Register")',
'button:has-text("Sign up")',
'button:has-text("Sign Up")',
'button:has-text("Create Account")'
];
let submitted = false;
for (const selector of submitSelectors) {
try {
await page.click(selector);
submitted = true;
break;
}
catch (e) {
// Try next selector
}
}
if (!submitted) {
throw new Error('Could not find submit button');
}
// Wait for navigation or timeout
try {
await page.waitForNavigation({ timeout: timeout / 2 });
}
catch (e) {
// Navigation might not happen, check for errors
}
// Wait a bit more for any redirects
await new Promise(resolve => setTimeout(resolve, 2000));
// Get final state
const finalUrl = page.url();
// Capture final destination
if (captureScreenshots) {
const finalScreenshot = await page.screenshot({ fullPage: true });
screenshots.push({
step: 'final_destination',
url: finalUrl,
screenshot: finalScreenshot.toString('base64')
});
}
// Clean up event handler
page.off('framenavigated', navigationHandler);
// Analyze results
const redirectMatches = expectedRedirect ?
new RegExp(expectedRedirect).test(finalUrl) : null;
return {
content: [{
type: 'text',
text: `## 🔍 Registration Flow Analysis
**Initial URL**: ${session.url}
**Final URL**: ${finalUrl}
**Expected Pattern**: ${expectedRedirect || 'Not specified'}
**Redirect Matches**: ${redirectMatches === null ? 'N/A' : redirectMatches ? '✅ Yes' : '❌ No'}
### Navigation Path
${navigationEvents.map((e, i) => `${i + 1}. ${e.url} (${new Date(e.timestamp).toISOString()})`).join('\n')}
### Timing
- Total navigation events: ${navigationEvents.length}
- Time to final destination: ${navigationEvents.length > 0 ? navigationEvents[navigationEvents.length - 1].timestamp - navigationEvents[0].timestamp : 0}ms
### Recommendations
${this.generateRegistrationFlowRecommendations(finalUrl, expectedRedirect, navigationEvents).map((r) => `- ${r}`).join('\n')}
### Screenshots Captured
${screenshots.map(s => `- ${s.step}: ${s.url}`).join('\n')}`
}]
};
}
catch (error) {
return {
content: [{
type: 'text',
text: `## ❌ Registration Flow Error
**Error**: ${error instanceof Error ? error.message : 'Unknown error'}
### Recommendations
- Check if the registration form structure has changed
- Verify form field selectors match your HTML
- Check for validation errors preventing submission
- Ensure the page is fully loaded before testing`
}]
};
}
}
async diagnoseLiveViewDeath(args, session) {
const { setupActions = [], monitorDuration = 5000, captureNetworkErrors = true, captureConsoleErrors = true } = args;
try {
const page = session.page;
if (!page) {
throw new Error('No page available in session');
}
// Perform setup actions
for (const action of setupActions) {
switch (action.action) {
case 'click':
await page.click(action.selector);
break;
case 'fill':
await page.fill(action.selector, action.value);
break;
case 'select':
await page.selectOption(action.selector, action.value);
break;
case 'wait':
await new Promise(resolve => setTimeout(resolve, action.timeout || 1000));
break;
}
}
// Start monitoring
const consoleErrors = [];
const networkFailures = [];
const websocketEvents = [];
// Monitor console
const consoleHandler = (msg) => {
if (msg.type() === 'error' || msg.type() === 'warning') {
consoleErrors.push({
level: msg.type(),
message: msg.text(),
timestamp: Date.now(),
location: msg.location()
});
}
};
if (captureConsoleErrors) {
page.on('console', consoleHandler);
}
// Monitor network
const responseHandler = (response) => {
const url = response.url();
const status = response.status();
if (status >= 400) {
networkFailures.push({
url,
method: response.request().method(),
status,
statusText: response.statusText(),
timestamp: Date.now()
});
}
// Track WebSocket events
if (url.includes('websocket') || url.includes('socket')) {
websocketEvents.push({
type: 'response',
url,
status,
timestamp: Date.now()
});
}
};
const requestFailedHandler = (request) => {
networkFailures.push({
url: request.url(),
method: request.method(),
failure: request.failure()?.errorText,
timestamp: Date.now()
});
};
if (captureNetworkErrors) {
page.on('response', responseHandler);
page.on('requestfailed', requestFailedHandler);
}
// Wait for monitoring period
await new Promise(resolve => setTimeout(resolve, monitorDuration));
// Clean up handlers
if (captureConsoleErrors) {
page.off('console', consoleHandler);
}
if (captureNetworkErrors) {
page.off('response', responseHandler);
page.off('requestfailed', requestFailedHandler);
}
// Analyze issues
const analysis = this.analyzeLiveViewIssues(consoleErrors, networkFailures, websocketEvents);
return {
content: [{
type: 'text',
text: `## 🩺 LiveView Death Diagnosis
### Console Errors (${consoleErrors.length})
${consoleErrors.slice(0, 10).map(e => `- [${e.level}] ${e.message}`).join('\n')}
${consoleErrors.length > 10 ? `... and ${consoleErrors.length - 10} more` : ''}
### Network Failures (${networkFailures.length})
${networkFailures.slice(0, 10).map(f => `- ${f.method || 'GET'} ${f.url} - ${f.status || f.failure}`).join('\n')}
${networkFailures.length > 10 ? `... and ${networkFailures.length - 10} more` : ''}
### GraphQL Errors (${analysis.graphqlProblems.length})
${analysis.graphqlProblems.join('\n')}
### WebSocket Events (${websocketEvents.length})
${websocketEvents.map(e => `- ${e.type} ${e.url} - Status: ${e.status}`).join('\n')}
### Analysis
**Likely Issues**: ${analysis.likelyIssues.length > 0 ? analysis.likelyIssues.join(', ') : 'None detected'}
**WebSocket Problems**: ${analysis.websocketProblems.join(', ') || 'None'}
### Recommendations
${analysis.recommendations.map((r) => `- ${r}`).join('\n')}
### Summary
- Total errors: ${consoleErrors.length + networkFailures.length}
- Monitoring duration: ${monitorDuration}ms
- GraphQL failures: ${networkFailures.filter(f => f.url?.includes('graphql')).length}
- WebSocket issues: ${websocketEvents.filter(e => e.status !== 200).length}`
}]
};
}
catch (error) {
return {
content: [{
type: 'text',
text: `## ❌ LiveView Diagnosis Error
**Error**: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
async captureWithVisualStability(args, session) {
const { stabilityThreshold = 500, waitConditions = [], maxWaitTime = 30000, annotations = [] } = args;
try {
const page = session.page;
if (!page) {
throw new Error('No page available in session');
}
const startTime = Date.now();
let isStable = false;
// Inject DOM stability monitor
await page.evaluate((threshold) => {
let observer;
window.__domStable = false;
window.__lastDOMChange = Date.now();
observer = new MutationObserver(() => {
window.__lastDOMChange = Date.now();
window.__domStable = false;
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
// Check stability periodically
const checkStability = setInterval(() => {
if (Date.now() - window.__lastDOMChange > threshold) {
window.__domStable = true;
clearInterval(checkStability);
observer.disconnect();
}
}, 100);
}, stabilityThreshold);
// Wait for specific conditions
for (const condition of waitConditions) {
await this.waitForCondition(page, condition, maxWaitTime - (Date.now() - startTime));
}
// Wait for DOM stability
while (!isStable && (Date.now() - startTime) < maxWaitTime) {
isStable = await page.evaluate(() => window.__domStable);
if (!isStable) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
// Take screenshot
const screenshot = await page.screenshot({ fullPage: true });
// Get timing information
const timingInfo = {
totalWaitTime: Date.now() - startTime,
domStabilityAchieved: isStable,
conditionsMet: waitConditions.map((c, i) => ({
condition: c,
met: true // If we got here, they were met
}))
};
return {
content: [{
type: 'text',
text: `## 📸 Visual Stability Capture
### Stability Status
- **DOM Stable**: ${isStable ? '✅ Yes' : '❌ No'}
- **Wait Time**: ${timingInfo.totalWaitTime}ms
- **Threshold**: ${stabilityThreshold}ms
### Wait Conditions
${waitConditions.length > 0 ? waitConditions.map((c, i) => `${i + 1}. ${c.type} - ${c.selector || c.text || 'custom'} ✅`).join('\n') : 'None specified'}
### Screenshot
- **URL**: ${page.url()}
- **Full Page**: Yes
- **Captured**: ${new Date().toISOString()}
${!isStable ? `
### ⚠️ Warning
DOM stability was not achieved within ${maxWaitTime}ms. The page may still be changing.
Consider:
- Increasing maxWaitTime
- Adjusting stabilityThreshold
- Adding specific wait conditions
` : ''}`
}],
screenshot: screenshot.toString('base64')
};
}
catch (error) {
return {
content: [{
type: 'text',
text: `## ❌ Visual Stability Capture Error
**Error**: ${error instanceof Error ? error.message : 'Unknown error'}
### Recommendations
- Increase maxWaitTime if the page loads slowly
- Adjust stabilityThreshold for pages with animations
- Add specific wait conditions for dynamic content`
}]
};
}
}
async debugFailedTest(args, session) {
const { testName, setupScript, includeAIAnalysis = true, captureBeforeFailure = true, focusAreas = ['graphql', 'websocket', 'async'] } = args;
try {
const page = session.page;
if (!page) {
throw new Error('No page available in session');
}
// Run setup script if provided
if (setupScript) {
await page.evaluate(setupScript);
}
// Capture initial state
const initialState = captureBeforeFailure ? {
screenshot: await page.screenshot({ fullPage: true }),
url: page.url(),
timestamp: Date.now()
} : null;
// Start comprehensive monitoring
const capturedData = {
console: [],
network: [],
errors: [],
performance: {}
};
// Set up console monitoring
const consoleHandler = (msg) => {
capturedData.console.push({
type: msg.type(),
text: msg.text(),
location: msg.location(),
timestamp: Date.now()
});
};
page.on('console', consoleHandler);
// Set up network monitoring
const responseHandler = (response) => {
const entry = {
url: response.url(),
method: response.request().method(),
status: response.status(),
type: response.request().resourceType(),
timestamp: Date.now()
};
capturedData.network.push(entry);
if (response.status() >= 400) {
capturedData.errors.push({
...entry,
type: 'network'
});
}
};
page.on('response', responseHandler);
// Monitor for 5 seconds to capture any async issues
await new Promise(resolve => setTimeout(resolve, 5000));
// Get performance metrics
capturedData.performance = await page.evaluate(() => {
const perf = performance.getEntriesByType('navigation')[0];
return {
domContentLoaded: perf?.domContentLoadedEventEnd - perf?.domContentLoadedEventStart,
loadComplete: perf?.loadEventEnd - perf?.loadEventStart,
totalTime: perf?.loadEventEnd - perf?.fetchStart
};
});
// Clean up handlers
page.off('console', consoleHandler);
page.off('response', responseHandler);
// Prepare analysis
const summary = this.generateTestFailureSummary(capturedData, focusAreas);
const recommendations = this.generateTestDebuggingRecommendations(capturedData, focusAreas);
return {
content: [{
type: 'text',
text: `## 🐛 Failed Test Debug Report
### Test: ${testName}
**URL**: ${page.url()}
**Timestamp**: ${new Date().toISOString()}
### Summary
- **Console Errors**: ${summary.consoleErrorCount}
- **Network Failures**: ${summary.networkFailureCount}
- **GraphQL Issues**: ${summary.graphqlIssues}
- **WebSocket Issues**: ${summary.websocketIssues}
- **Total Errors**: ${summary.errorCount}
### Focus Areas Analysis
${focusAreas.includes('graphql') ? `
#### GraphQL
- Failed requests: ${capturedData.network.filter(n => n.url?.includes('graphql') && n.status >= 400).length}
- GraphQL errors: ${capturedData.console.filter(c => c.text?.toLowerCase().includes('graphql')).length}
` : ''}
${focusAreas.includes('websocket') ? `
#### WebSocket
- WebSocket errors: ${capturedData.network.filter(n => n.url?.includes('socket') && n.status >= 400).length}
- Connection issues: ${capturedData.console.filter(c => c.text?.toLowerCase().includes('websocket') || c.text?.toLowerCase().includes('connection')).length}
` : ''}
${focusAreas.includes('async') ? `
#### Async Operations
- Unhandled rejections: ${capturedData.console.filter(c => c.text?.includes('Unhandled') || c.text?.includes('rejection')).length}
- Timeout errors: ${capturedData.console.filter(c => c.text?.toLowerCase().includes('timeout')).length}
` : ''}
### Console Output (Last 10 Errors)
${capturedData.console
.filter(c => c.type === 'error')
.slice(-10)
.map(c => `- ${c.text}`)
.join('\n') || 'No console errors captured'}
### Failed Network Requests
${capturedData.network
.filter(n => n.status >= 400)
.slice(0, 10)
.map(n => `- ${n.method} ${n.url} - ${n.status}`)
.join('\n') || 'No failed requests'}
### Performance Metrics
- DOM Content Loaded: ${capturedData.performance.domContentLoaded}ms
- Page Load Complete: ${capturedData.performance.loadComplete}ms
- Total Load Time: ${capturedData.performance.totalTime}ms
### Recommendations
${recommendations.map((r) => `- ${r}`).join('\n')}
${includeAIAnalysis ? `
### AI Analysis Note
For deeper AI-powered analysis, use the \`analyze_with_ai\` tool with the captured data.
` : ''}`
}]
};
}
catch (error) {
return {
content: [{
type: 'text',
text: `## ❌ Test Debug Error
**Error**: ${error instanceof Error ? error.message : 'Unknown error'}
**Test**: ${testName}`
}]
};
}
}
// Helper methods
async waitForCondition(page, condition, timeout) {
const startTime = Date.now();
while ((Date.now() - startTime) < timeout) {
let conditionMet = false;
switch (condition.type) {
case 'element':
conditionMet = await page.evaluate((selector) => {
return document.querySelector(selector) !== null;
}, condition.selector);
break;
case 'text':
conditionMet = await page.evaluate((args) => {
const element = document.querySelector(args.selector);
return element?.textContent?.includes(args.text) || false;
}, { selector: condition.selector, text: condition.text });
break;
case 'animation':
conditionMet = await page.evaluate(() => {
const animations = document.getAnimations();
return animations.every((a) => a.playState !== 'running');
});
break;
case 'network':
// For now, just wait - would need to track network activity
await new Promise(resolve => setTimeout(resolve, 1000));
conditionMet = true;
break;
case 'custom':
conditionMet = await page.evaluate(condition.customScript);
break;
}
if (conditionMet)
return;
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`Condition timeout: ${JSON.stringify(condition)}`);
}
generateRegistrationFlowRecommendations(finalUrl, expectedRedirect, navigationEvents) {
const recommendations = [];
if (expectedRedirect && !new RegExp(expectedRedirect).test(finalUrl)) {
recommendations.push(`Update test expectation: Registration redirects to "${finalUrl}" not "${expectedRedirect}"`);
}
if (navigationEvents.length > 2) {
recommendations.push(`Multiple redirects detected (${navigationEvents.length}). Consider simplifying the flow.`);
}
if (navigationEvents.some(e => e.url?.includes('error') || e.url?.includes('fail'))) {
recommendations.push('Registration may be failing. Check form validation and server responses.');
}
if (navigationEvents.length === 1) {
recommendations.push('No navigation occurred. Check if form submission is working correctly.');
}
return recommendations;
}
analyzeLiveViewIssues(consoleErrors, networkFailures, websocketEvents) {
const analysis = {
likelyIssues: [],
graphqlProblems: [],
websocketProblems: [],
recommendations: []
};
// Analyze GraphQL issues
const graphqlErrors = networkFailures.filter(f => f.url?.includes('graphql'));
if (graphqlErrors.length > 0) {
analysis.graphqlProblems = graphqlErrors.map(e => `- GraphQL ${e.method} to ${e.url} failed with status ${e.status}`);
analysis.recommendations.push('Check GraphQL mocks are available to async processes');
analysis.recommendations.push('Verify GraphQL schema matches between tests and application');
}
// Analyze WebSocket issues
const wsFailures = networkFailures.filter(f => f.url?.includes('socket'));
if (wsFailures.length > 0 || websocketEvents.some(e => e.status !== 101 && e.status !== 200)) {
analysis.websocketProblems.push('WebSocket connection issues detected');
analysis.recommendations.push('Ensure LiveView process isn\'t crashing');
analysis.recommendations.push('Check Phoenix endpoint configuration');
}
// Analyze console errors
const liveViewErrors = consoleErrors.filter(e => e.message?.toLowerCase().includes('liveview') ||
e.message?.toLowerCase().includes('phoenix') ||
e.message?.toLowerCase().includes('channel'));
if (liveViewErrors.length > 0) {
analysis.likelyIssues.push('LiveView JavaScript errors detected');
analysis.recommendations.push('Check LiveView hooks and event handlers');
}
// Check for process crashes
if (consoleErrors.some(e => e.message?.includes('disconnected') || e.message?.includes('terminated'))) {
analysis.likelyIssues.push('Process disconnection/termination detected');
analysis.recommendations.push('Check server logs for process crashes');
}
return analysis;
}
generateTestFailureSummary(capturedData, focusAreas) {
return {
errorCount: capturedData.errors.length,
consoleErrorCount: capturedData.console.filter((c) => c.type === 'error').length,
networkFailureCount: capturedData.network.filter((n) => n.status >= 400).length,
graphqlIssues: focusAreas.includes('graphql') ?
capturedData.network.filter((n) => n.url?.includes('graphql') && n.status >= 400).length : 0,
websocketIssues: focusAreas.includes('websocket') ?
capturedData.network.filter((n) => n.url?.includes('socket') && n.status >= 400).length : 0
};
}
generateTestDebuggingRecommendations(capturedData, focusAreas) {
const recommendations = [];
if (capturedData.network.some((n) => n.url?.includes('graphql') && n.status >= 400)) {
recommendations.push('GraphQL requests are failing - verify mocks are set up for all queries');
recommendations.push('Check if GraphQL mocks are accessible from async processes');
}
if (capturedData.console.some((c) => c.text?.includes('channel') || c.text?.includes('socket'))) {
recommendations.push('Phoenix channel errors detected - check LiveView process lifecycle');
recommendations.push('Verify WebSocket connection is established before interactions');
}
if (capturedData.errors.length > 0) {
recommendations.push(`${capturedData.errors.length} errors detected - review error messages in console`);
}
if (capturedData.console.some((c) => c.text?.includes('Unhandled'))) {
recommendations.push('Unhandled promise rejections detected - add proper error handling');
}
if (focusAreas.includes('async') && capturedData.console.some((c) => c.text?.includes('timeout'))) {
recommendations.push('Timeout errors detected - consider increasing test timeouts');
}
return recommendations;
}
}
//# sourceMappingURL=test-debugging-helper-handler.js.map