Skip to main content
Glama
console-service.test.ts7.04 kB
import { strict as assert } from 'node:assert'; import { after, before, describe, it } from 'node:test'; import { ConsoleLogService } from '../../src/console-service.js'; import type { ConsoleLogServiceConfig, ConsoleLogEntry, } from '../../src/types.js'; describe('ConsoleLogService', () => { let service: ConsoleLogService; let mockConfig: ConsoleLogServiceConfig; before(() => { mockConfig = { console: { defaultTimeout: 30000, defaultSanitize: true, defaultWaitForNetworkIdle: true, maxLogSize: 1048576, defaultLogLevels: ['log', 'info', 'warn', 'error', 'debug'] as Array< 'log' | 'info' | 'warn' | 'error' | 'debug' >, }, browser: { retryCount: 3, retryDelay: 1000, }, authentication: { defaultCookies: [], }, logging: { debug: false, enableMetrics: true, silent: true, }, timeouts: { browserInit: 30000, navigation: 30000, elementWait: 5000, screenshot: 10000, network: 5000, }, }; service = new ConsoleLogService(mockConfig); }); after(async () => { if (service) { await service.cleanup(); } }); describe('constructor', () => { it('should create a ConsoleLogService instance', () => { assert.ok(service instanceof ConsoleLogService); }); it('should initialize with healthy state as false', () => { assert.strictEqual(service.isHealthy(), false); }); }); describe('initialize', () => { it('should initialize service successfully', async () => { await service.initialize(); assert.strictEqual(service.isHealthy(), true); }); it('should handle multiple initialization attempts gracefully', async () => { const wasHealthy = service.isHealthy(); await service.initialize(); assert.strictEqual(service.isHealthy(), wasHealthy); }); }); describe('readConsoleLogs', () => { it('should read console logs from a test page', async () => { // Test with a local file URL that generates console output const testUrl = 'data:text/html,<html><head></head><body><script>console.log("test log");console.info("test info");console.warn("test warning");console.error("test error");console.debug("test debug");</script></body></html>'; const result = await service.readConsoleLogs({ url: testUrl, timeout: 5000, sanitize: false, waitForNetworkIdle: false, }); // Verify result structure assert.ok(result); assert.ok(Array.isArray(result.logs)); assert.strictEqual(typeof result.url, 'string'); assert.strictEqual(result.url, testUrl); assert.strictEqual(typeof result.startTimestamp, 'number'); assert.strictEqual(typeof result.endTimestamp, 'number'); assert.strictEqual(typeof result.totalLogs, 'number'); assert.strictEqual(result.totalLogs, result.logs.length); // Check log structure and filter by levels const logMessages = result.logs.filter( (log: ConsoleLogEntry) => log.level === 'log' ); const infoMessages = result.logs.filter( (log: ConsoleLogEntry) => log.level === 'info' ); const warnMessages = result.logs.filter( (log: ConsoleLogEntry) => log.level === 'warn' ); const errorMessages = result.logs.filter( (log: ConsoleLogEntry) => log.level === 'error' ); const debugMessages = result.logs.filter( (log: ConsoleLogEntry) => log.level === 'debug' ); // Should have captured different types of console output assert.ok( logMessages.length > 0 || infoMessages.length > 0 || warnMessages.length > 0 || errorMessages.length > 0 || debugMessages.length > 0 ); // Verify each log entry has the right structure result.logs.forEach((log: ConsoleLogEntry) => { assert.strictEqual(typeof log.timestamp, 'number'); assert.ok( ['log', 'info', 'warn', 'error', 'debug'].includes(log.level) ); assert.strictEqual(typeof log.message, 'string'); assert.ok(Array.isArray(log.args)); }); }); it('should sanitize sensitive information when enabled', async () => { // Test with a local URL that includes patterns that should be sanitized const longApiKey = 'abcdef123456789012345678901234567890'; // 36 chars, should match API key pattern const emailAddress = 'user@example.com'; const testUrl = `data:text/html,<html><head></head><body><script>console.log("API key: ${longApiKey}");console.log("Email: ${emailAddress}");</script></body></html>`; const result = await service.readConsoleLogs({ url: testUrl, timeout: 5000, sanitize: true, waitForNetworkIdle: false, }); // Check that sensitive patterns are masked const allLogText = result.logs .map((log: ConsoleLogEntry) => `${log.message} ${log.args.join(' ')}`) .join(' '); // Should not contain the original sensitive data if logs were captured if (result.logs.length > 0) { assert.ok( !allLogText.includes(longApiKey) || !allLogText.includes(emailAddress) ); // Should contain masked markers if sensitive data was found assert.ok( allLogText.includes('[API_KEY_MASKED]') || allLogText.includes('[EMAIL_MASKED]') || result.logs.length === 0 ); } }); }); describe('error handling', () => { it('should handle invalid URL', async () => { try { await service.readConsoleLogs({ url: 'invalid-url-format', timeout: 5000, }); assert.fail('Should have thrown an error for invalid URL'); } catch (error) { assert.ok(error instanceof Error); assert.ok( error.message.includes('URL') || error.message.includes('Protocol') || error.message.includes('Invalid') ); } }); it('should handle timeout', async () => { try { // Use a very short timeout with a URL that might take longer to load await service.readConsoleLogs({ url: 'data:text/html,<html><head></head><body><script>setTimeout(() => console.log("delayed"), 2000);</script></body></html>', timeout: 100, // Very short timeout }); // If it doesn't timeout, that's actually fine too } catch (error) { assert.ok(error instanceof Error); // Should be a timeout-related error assert.ok( error.message.includes('timeout') || error.message.includes('Timeout') ); } }); }); describe('cleanup', () => { it('should cleanup resources and set healthy state to false', async () => { await service.cleanup(); assert.strictEqual(service.isHealthy(), false); }); }); });

Latest Blog Posts

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/mattiasw/browserloop'

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