Skip to main content
Glama

Puppeteer Real Browser MCP Server

by withLinda
browser-handlers.test.ts•16.4 kB
/** * Unit Tests for Browser Handlers * * Following TDD Red-Green-Refactor methodology with 2025 best practices: * - AAA Pattern (Arrange-Act-Assert) * - Comprehensive mocking of dependencies * - Workflow validation and error handling testing * - Browser initialization and cleanup testing */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleBrowserInit, handleBrowserClose } from './browser-handlers.js'; import { BrowserInitArgs } from '../tool-definitions.js'; // Mock all external dependencies vi.mock('../browser-manager', () => ({ initializeBrowser: vi.fn(), closeBrowser: vi.fn(), getBrowserInstance: vi.fn(), getPageInstance: vi.fn(), getContentPriorityConfig: vi.fn(), updateContentPriorityConfig: vi.fn() })); vi.mock('../system-utils', () => ({ withErrorHandling: vi.fn(async (operation, errorMessage) => await operation()) })); vi.mock('../workflow-validation', () => ({ validateWorkflow: vi.fn(), recordExecution: vi.fn(), workflowValidator: { reset: vi.fn(), getValidationSummary: vi.fn() } })); // Import mocked modules import * as browserManager from '../browser-manager.js'; import * as systemUtils from '../system-utils.js'; import * as workflowValidation from '../workflow-validation.js'; describe('Browser Handlers', () => { let mockBrowserManager: any; let mockSystemUtils: any; let mockWorkflowValidation: any; beforeEach(() => { vi.clearAllMocks(); // Setup mocks mockBrowserManager = browserManager; mockSystemUtils = systemUtils; mockWorkflowValidation = workflowValidation; // Default mock implementations mockWorkflowValidation.validateWorkflow.mockReturnValue({ isValid: true, errorMessage: null, suggestedAction: null }); mockBrowserManager.getContentPriorityConfig.mockReturnValue({ prioritizeContent: true, autoSuggestGetContent: true }); // Ensure updateContentPriorityConfig is properly mocked mockBrowserManager.updateContentPriorityConfig.mockReturnValue(undefined); mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); mockBrowserManager.closeBrowser.mockResolvedValue(undefined); // Ensure workflow validation functions are properly mocked mockWorkflowValidation.recordExecution.mockReturnValue(undefined); mockWorkflowValidation.workflowValidator.reset.mockReturnValue(undefined); mockWorkflowValidation.workflowValidator.getValidationSummary.mockReturnValue('Mock summary'); }); describe('Browser Initialization', () => { it('should initialize browser successfully with default settings', async () => { // Arrange: Basic browser init args const args: BrowserInitArgs = { headless: false, disableXvfb: false }; mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); // Act: Initialize browser const result = await handleBrowserInit(args); // Assert: Should initialize browser and return success message expect(mockBrowserManager.initializeBrowser).toHaveBeenCalledWith( expect.objectContaining({ headless: false }) ); expect(mockWorkflowValidation.validateWorkflow).toHaveBeenCalledWith('browser_init', expect.objectContaining({ headless: false }) ); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith('browser_init', expect.objectContaining({ headless: false }), true); expect(result).toHaveProperty('content'); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toContain('Browser initialized successfully'); expect(result.content[0].text).toContain('Content Priority Mode'); // Should show because prioritizeContent is true expect(result.content[0].text).toContain('Workflow Status: Browser initialized'); }); it('should update content priority configuration when provided', async () => { // Arrange: Browser init args with content priority config const args: BrowserInitArgs = { headless: true, contentPriority: { prioritizeContent: false, autoSuggestGetContent: false } }; // Mock the complete flow mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); mockBrowserManager.getContentPriorityConfig.mockReturnValue({ prioritizeContent: false, autoSuggestGetContent: false }); // Act: Initialize browser with custom config const result = await handleBrowserInit(args); // Assert: Should update content priority config with the exact args passed expect(mockBrowserManager.updateContentPriorityConfig).toHaveBeenCalledWith( expect.objectContaining({ prioritizeContent: false, autoSuggestGetContent: false }) ); expect(mockBrowserManager.getContentPriorityConfig).toHaveBeenCalled(); expect(result.content[0].text).not.toContain('Content Priority Mode'); }); it('should handle browser initialization failure', async () => { // Arrange: Browser init args that will cause failure const args: BrowserInitArgs = { headless: false }; const initError = new Error('Failed to start browser'); mockBrowserManager.initializeBrowser.mockRejectedValue(initError); mockSystemUtils.withErrorHandling.mockImplementation(async (operation: () => Promise<any>, errorMessage: string) => { try { return await operation(); } catch (error) { throw new Error(errorMessage); } }); // Act & Assert: Should throw error await expect(handleBrowserInit(args)).rejects.toThrow('Failed to initialize browser'); expect(mockBrowserManager.initializeBrowser).toHaveBeenCalledWith(args); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_init', args, false, 'Failed to initialize browser' ); }); it('should handle workflow validation failure', async () => { // Arrange: Clear mocks for isolated test and set invalid workflow state vi.clearAllMocks(); // Set up minimal required mocks for this test mockWorkflowValidation.validateWorkflow.mockReturnValue({ isValid: false, errorMessage: 'Browser already initialized', suggestedAction: 'Close browser first' }); mockWorkflowValidation.workflowValidator.getValidationSummary.mockReturnValue( 'Current state: BROWSER_ACTIVE | Last action: browser_init' ); mockWorkflowValidation.recordExecution.mockReturnValue(undefined); // Ensure initializeBrowser is not called by clearing its mock mockBrowserManager.initializeBrowser.mockClear(); const args: BrowserInitArgs = { headless: false }; // Act & Assert: Should throw workflow validation error await expect(handleBrowserInit(args)).rejects.toThrow(/Browser already initialized.*Next Steps: Close browser first/s); // Verify that browser initialization was NOT called due to validation failure expect(mockBrowserManager.initializeBrowser).not.toHaveBeenCalled(); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_init', expect.objectContaining({ headless: false }), false, expect.stringContaining('Browser already initialized') ); }); it('should include workflow guidance in success message', async () => { // Arrange: Set up for successful initialization (keep existing mock setup) mockWorkflowValidation.validateWorkflow.mockReturnValue({ isValid: true, errorMessage: null, suggestedAction: null }); mockBrowserManager.getContentPriorityConfig.mockReturnValue({ prioritizeContent: true, autoSuggestGetContent: true }); const args: BrowserInitArgs = { headless: false }; mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); // Act: Initialize browser const result = await handleBrowserInit(args); // Assert: Should include comprehensive workflow guidance expect(result.content[0].text).toContain('Next step: Use navigate to load a web page'); expect(result.content[0].text).toContain('Then: Use get_content to analyze page content'); expect(result.content[0].text).toContain('Finally: Use find_selector and interaction tools'); expect(result.content[0].text).toContain('Workflow validation is now active'); expect(result.content[0].text).toContain('prevents blind selector guessing'); }); }); describe('Browser Close', () => { it('should close browser successfully', async () => { // Arrange: Setup for browser close mockBrowserManager.closeBrowser.mockResolvedValue(undefined); // Act: Close browser const result = await handleBrowserClose(); // Assert: Should close browser and reset workflow expect(mockBrowserManager.closeBrowser).toHaveBeenCalled(); expect(mockWorkflowValidation.workflowValidator.reset).toHaveBeenCalled(); expect(mockWorkflowValidation.validateWorkflow).toHaveBeenCalledWith('browser_close', {}); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith('browser_close', {}, true); expect(result).toHaveProperty('content'); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toContain('Browser closed successfully'); expect(result.content[0].text).toContain('Workflow state reset'); }); it('should handle browser close failure', async () => { // Arrange: Browser close that will fail const closeError = new Error('Failed to close browser'); mockBrowserManager.closeBrowser.mockRejectedValue(closeError); mockSystemUtils.withErrorHandling.mockImplementation(async (operation: () => Promise<any>, errorMessage: string) => { try { return await operation(); } catch (error) { throw new Error(errorMessage); } }); // Act & Assert: Should throw error await expect(handleBrowserClose()).rejects.toThrow('Failed to close browser'); expect(mockBrowserManager.closeBrowser).toHaveBeenCalled(); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_close', {}, false, 'Failed to close browser' ); }); it('should handle workflow validation failure for close', async () => { // Arrange: Invalid workflow state for close mockWorkflowValidation.validateWorkflow.mockReturnValue({ isValid: false, errorMessage: 'No browser to close', suggestedAction: 'Initialize browser first' }); mockWorkflowValidation.workflowValidator.getValidationSummary.mockReturnValue( 'Current state: BROWSER_INIT | No browser instance' ); // Act & Assert: Should throw workflow validation error await expect(handleBrowserClose()).rejects.toThrow(/No browser to close.*Next Steps: Initialize browser first/s); expect(mockBrowserManager.closeBrowser).not.toHaveBeenCalled(); expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_close', {}, false, expect.stringContaining('No browser to close') ); }); it('should always reset workflow state even if close fails', async () => { // Arrange: Browser close failure but reset should still happen mockBrowserManager.closeBrowser.mockRejectedValue(new Error('Close failed')); mockSystemUtils.withErrorHandling.mockImplementation(async (operation: () => Promise<any>) => { try { await operation(); // Reset happens in the operation, so we need to call it mockWorkflowValidation.workflowValidator.reset(); } catch (error) { // Reset should happen even on error mockWorkflowValidation.workflowValidator.reset(); throw error; } }); // Act: Attempt to close browser (will fail) try { await handleBrowserClose(); } catch (error) { // Expected to fail } // Assert: Workflow should still be reset expect(mockWorkflowValidation.workflowValidator.reset).toHaveBeenCalled(); }); }); describe('Workflow Validation Wrapper', () => { it('should validate workflow before executing operation', async () => { // Arrange: Valid workflow state const args: BrowserInitArgs = { headless: false }; mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); // Act: Execute browser init (uses workflow validation) await handleBrowserInit(args); // Assert: Should validate workflow first expect(mockWorkflowValidation.validateWorkflow).toHaveBeenCalled(); expect(mockBrowserManager.initializeBrowser).toHaveBeenCalled(); }); it('should record execution results for successful operations', async () => { // Arrange: Successful operation const args: BrowserInitArgs = { headless: false }; mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); // Act: Execute successful operation await handleBrowserInit(args); // Assert: Should record successful execution expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_init', args, true ); }); it('should record execution results for failed operations', async () => { // Arrange: Operation that will fail const args: BrowserInitArgs = { headless: false }; const error = new Error('Operation failed'); mockBrowserManager.initializeBrowser.mockRejectedValue(error); mockSystemUtils.withErrorHandling.mockImplementation(async (operation: () => Promise<any>) => { return await operation(); }); // Act: Execute failing operation try { await handleBrowserInit(args); } catch (err) { // Expected to fail } // Assert: Should record failed execution expect(mockWorkflowValidation.recordExecution).toHaveBeenCalledWith( 'browser_init', args, false, 'Operation failed' ); }); it('should include workflow summary in validation errors', async () => { // Arrange: Invalid workflow with detailed summary const args: BrowserInitArgs = { headless: false }; mockWorkflowValidation.validateWorkflow.mockReturnValue({ isValid: false, errorMessage: 'Invalid workflow state', suggestedAction: 'Reset workflow' }); mockWorkflowValidation.workflowValidator.getValidationSummary.mockReturnValue( 'Current state: INVALID | Last action: unknown | Context: missing' ); // Act & Assert: Should include detailed workflow information await expect(handleBrowserInit(args)).rejects.toThrow( /Invalid workflow state.*Next Steps: Reset workflow.*Current state: INVALID/s ); }); }); describe('Error Handling Integration', () => { it('should use system error handling wrapper', async () => { // Arrange: Browser init with error handling const args: BrowserInitArgs = { headless: false }; mockBrowserManager.initializeBrowser.mockResolvedValue(undefined); // Act: Execute browser init await handleBrowserInit(args); // Assert: Should use error handling wrapper expect(mockSystemUtils.withErrorHandling).toHaveBeenCalledWith( expect.any(Function), 'Failed to initialize browser' ); }); it('should provide appropriate error context for different operations', async () => { // Arrange: Browser close operation mockBrowserManager.closeBrowser.mockResolvedValue(undefined); // Act: Execute browser close await handleBrowserClose(); // Assert: Should use specific error message for close operation expect(mockSystemUtils.withErrorHandling).toHaveBeenCalledWith( expect.any(Function), 'Failed to close browser' ); }); }); });

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/withLinda/puppeteer-real-browser-mcp-server'

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