/**
* Variable Inspection End-to-End Tests
*
* Tests the complete variable inspection system including MCP tools,
* DebuggerCore integration, and real-time functionality.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { DebuggerCore } from '../src/core/DebuggerCore.js';
import { ConfigManager } from '../src/config/ConfigManager.js';
import { VariableCommand } from '../src/managers/VariableManager.js';
// Mock all dependencies
vi.mock('../src/config/ConfigManager.js');
vi.mock('../src/debuggers/ChromeDebugger.js');
vi.mock('../src/watchers/FileWatcher.js');
vi.mock('../src/analyzers/CodeAnalyzer.js');
vi.mock('../src/monitors/PerformanceMonitor.js');
vi.mock('../src/trackers/ErrorTracker.js');
vi.mock('../src/streams/StreamManager.js');
vi.mock('../src/managers/BreakpointManager.js');
describe('Variable Inspection E2E', () => {
let debuggerCore: DebuggerCore;
let mockConfigManager: ConfigManager;
const mockConfig = {
browser: { host: 'localhost', port: 9222 },
variables: {
maxWatchExpressions: 10,
autoEvaluateWatches: true,
cacheVariables: true
}
};
beforeEach(async () => {
vi.clearAllMocks();
mockConfigManager = new ConfigManager() as any;
mockConfigManager.getConfig = vi.fn().mockReturnValue(mockConfig);
mockConfigManager.initialize = vi.fn().mockResolvedValue(undefined);
mockConfigManager.on = vi.fn();
debuggerCore = new DebuggerCore(mockConfigManager);
// Mock the session state
(debuggerCore as any).session.browserConnected = true;
// Mock the chrome debugger
const mockChromeDebugger = (debuggerCore as any).chromeDebugger;
mockChromeDebugger.isPausedState = vi.fn().mockReturnValue(true);
mockChromeDebugger.getTopCallFrame = vi.fn().mockReturnValue({
callFrameId: 'frame_123'
});
mockChromeDebugger.getScopeChain = vi.fn().mockResolvedValue([
{
type: 'local',
objectId: 'scope_local_123',
variableCount: 2
}
]);
mockChromeDebugger.getVariablesInScope = vi.fn().mockResolvedValue([
{
name: 'localVar',
value: 'test value',
type: 'string',
description: 'test value',
writable: true,
configurable: true,
enumerable: true,
isOwn: true
}
]);
mockChromeDebugger.evaluateOnCallFrame = vi.fn().mockResolvedValue({
result: 'evaluated result',
type: 'string',
description: 'evaluated result',
wasThrown: false
});
mockChromeDebugger.setVariableValue = vi.fn().mockResolvedValue(undefined);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Variable Inspection Workflow', () => {
it('should complete full variable inspection workflow', async () => {
// 1. Get scope chain
const scopeResult = await debuggerCore.getScopeChain();
expect(scopeResult.success).toBe(true);
expect(scopeResult.scopes).toHaveLength(1);
expect(scopeResult.scopes![0].type).toBe('local');
// 2. Inspect variables in local scope
const inspectCommand: VariableCommand = {
action: 'inspect',
callFrameId: 'frame_123',
scopeNumber: 0
};
const inspectResult = await debuggerCore.executeVariableCommand(inspectCommand);
expect(inspectResult.success).toBe(true);
expect(inspectResult.variables).toHaveLength(1);
expect(inspectResult.variables![0].name).toBe('localVar');
// 3. Evaluate expression
const evaluateCommand: VariableCommand = {
action: 'evaluate',
expression: 'localVar + " modified"',
callFrameId: 'frame_123'
};
const evaluateResult = await debuggerCore.executeVariableCommand(evaluateCommand);
expect(evaluateResult.success).toBe(true);
expect(evaluateResult.evaluationResult?.result).toBe('evaluated result');
// 4. Modify variable
const modifyCommand: VariableCommand = {
action: 'modify',
callFrameId: 'frame_123',
scopeNumber: 0,
variableName: 'localVar',
newValue: 'new value'
};
const modifyResult = await debuggerCore.executeVariableCommand(modifyCommand);
expect(modifyResult.success).toBe(true);
});
it('should handle watch expression lifecycle', async () => {
// Add watch expression
const addWatchCommand: VariableCommand = {
action: 'watch',
expression: 'localVar * 2',
watchAction: 'add'
};
const addResult = await debuggerCore.executeVariableCommand(addWatchCommand);
expect(addResult.success).toBe(true);
expect(addResult.watchExpressions).toHaveLength(1);
const watchId = addResult.watchExpressions![0].id;
// List watch expressions
const listWatchCommand: VariableCommand = {
action: 'watch',
watchAction: 'list'
};
const listResult = await debuggerCore.executeVariableCommand(listWatchCommand);
expect(listResult.success).toBe(true);
expect(listResult.watchExpressions).toHaveLength(1);
// Evaluate watch expressions
const evaluateWatchCommand: VariableCommand = {
action: 'watch',
callFrameId: 'frame_123',
watchAction: 'evaluate'
};
const evaluateResult = await debuggerCore.executeVariableCommand(evaluateWatchCommand);
expect(evaluateResult.success).toBe(true);
// Remove watch expression
const removeWatchCommand: VariableCommand = {
action: 'watch',
watchId,
watchAction: 'remove'
};
const removeResult = await debuggerCore.executeVariableCommand(removeWatchCommand);
expect(removeResult.success).toBe(true);
});
});
describe('Error Scenarios', () => {
it('should handle debugger not connected', async () => {
(debuggerCore as any).session.browserConnected = false;
const command: VariableCommand = {
action: 'inspect'
};
const result = await debuggerCore.executeVariableCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Chrome debugger not connected');
});
it('should handle debugger not paused for inspection', async () => {
const mockChromeDebugger = (debuggerCore as any).chromeDebugger;
mockChromeDebugger.isPausedState = vi.fn().mockReturnValue(false);
const command: VariableCommand = {
action: 'inspect'
};
const result = await debuggerCore.executeVariableCommand(command);
expect(result.success).toBe(false);
expect(result.message).toContain('Debugger is not paused');
});
it('should handle missing call frame', async () => {
const mockChromeDebugger = (debuggerCore as any).chromeDebugger;
mockChromeDebugger.getTopCallFrame = vi.fn().mockReturnValue(null);
const scopeResult = await debuggerCore.getScopeChain();
expect(scopeResult.success).toBe(false);
expect(scopeResult.message).toContain('No call frame available');
});
it('should handle Chrome DevTools errors', async () => {
const mockChromeDebugger = (debuggerCore as any).chromeDebugger;
mockChromeDebugger.getScopeChain = vi.fn().mockRejectedValue(new Error('CDP connection lost'));
const scopeResult = await debuggerCore.getScopeChain('frame_123');
expect(scopeResult.success).toBe(false);
expect(scopeResult.message).toContain('CDP connection lost');
});
});
describe('Performance and Caching', () => {
it('should cache variable inspection results', async () => {
const mockVariableManager = (debuggerCore as any).variableManager;
// Mock cache behavior
const mockCacheStats = { size: 1, keys: ['frame_123-0'] };
mockVariableManager.getCacheStats = vi.fn().mockReturnValue(mockCacheStats);
const command: VariableCommand = {
action: 'inspect',
callFrameId: 'frame_123',
scopeNumber: 0
};
// First call
await debuggerCore.executeVariableCommand(command);
// Second call should use cache
await debuggerCore.executeVariableCommand(command);
const cacheStats = mockVariableManager.getCacheStats();
expect(cacheStats.size).toBe(1);
expect(cacheStats.keys).toContain('frame_123-0');
});
it('should handle large numbers of watch expressions', async () => {
const maxWatches = 5;
// Add maximum number of watch expressions
for (let i = 0; i < maxWatches; i++) {
const command: VariableCommand = {
action: 'watch',
expression: `variable${i}`,
watchAction: 'add'
};
const result = await debuggerCore.executeVariableCommand(command);
expect(result.success).toBe(true);
}
// List all watches
const listCommand: VariableCommand = {
action: 'watch',
watchAction: 'list'
};
const listResult = await debuggerCore.executeVariableCommand(listCommand);
expect(listResult.success).toBe(true);
expect(listResult.watchExpressions).toHaveLength(maxWatches);
});
});
describe('Real-time Updates', () => {
it('should handle debugger paused events', async () => {
const mockVariableManager = (debuggerCore as any).variableManager;
const pausedSpy = vi.fn();
mockVariableManager.on = vi.fn().mockImplementation((event, callback) => {
if (event === 'debuggerPaused') {
pausedSpy.mockImplementation(callback);
}
});
// Simulate debugger paused event
const pausedData = { callFrames: [{ callFrameId: 'frame_123' }] };
mockVariableManager.emit('debuggerPaused', pausedData);
expect(pausedSpy).toHaveBeenCalledWith(pausedData);
});
it('should auto-evaluate watch expressions on pause', async () => {
const mockVariableManager = (debuggerCore as any).variableManager;
// Add a watch expression
await debuggerCore.executeVariableCommand({
action: 'watch',
expression: 'counter',
watchAction: 'add'
});
// Simulate debugger paused event (should trigger auto-evaluation)
const pausedData = { callFrames: [{ callFrameId: 'frame_123' }] };
mockVariableManager.emit('debuggerPaused', pausedData);
// Verify watch expressions were evaluated
const listResult = await debuggerCore.executeVariableCommand({
action: 'watch',
watchAction: 'list'
});
expect(listResult.success).toBe(true);
expect(listResult.watchExpressions).toHaveLength(1);
});
});
describe('Configuration Management', () => {
it('should respect variable manager configuration', async () => {
const mockVariableManager = (debuggerCore as any).variableManager;
const mockConfig = {
maxWatchExpressions: 3,
autoEvaluateWatches: false,
cacheVariables: false
};
mockVariableManager.getConfig = vi.fn().mockReturnValue(mockConfig);
const config = mockVariableManager.getConfig();
expect(config.maxWatchExpressions).toBe(3);
expect(config.autoEvaluateWatches).toBe(false);
expect(config.cacheVariables).toBe(false);
});
it('should handle configuration updates', async () => {
const mockVariableManager = (debuggerCore as any).variableManager;
// Simulate configuration change
const configChangedSpy = vi.fn();
mockVariableManager.on = vi.fn().mockImplementation((event, callback) => {
if (event === 'configChanged') {
configChangedSpy.mockImplementation(callback);
}
});
mockConfigManager.emit('configChanged');
expect(configChangedSpy).toHaveBeenCalled();
});
});
});