/** * @claude-flow/browser - Agent Browser Adapter Tests */ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { AgentBrowserAdapter } from '../src/infrastructure/agent-browser-adapter.js'; import { execFileSync } from 'child_process'; vi.mock('child_process', () => ({ execSync: vi.fn(), execFileSync: vi.fn(), spawn: vi.fn(), })); const mockExecSync = vi.mocked(execFileSync); describe('AgentBrowserAdapter', () => { let adapter: AgentBrowserAdapter; beforeEach(() => { adapter = new AgentBrowserAdapter({ session: 'test-session', timeout: 5000, }); vi.clearAllMocks(); }); afterEach(() => { vi.clearAllMocks(); }); describe('constructor', () => { it('should create adapter with default options', () => { const defaultAdapter = new AgentBrowserAdapter(); expect(defaultAdapter).toBeInstanceOf(AgentBrowserAdapter); }); it('should create adapter with custom options', () => { const customAdapter = new AgentBrowserAdapter({ session: 'custom', timeout: 10000, headless: false, debug: true, }); expect(customAdapter).toBeInstanceOf(AgentBrowserAdapter); }); }); describe('navigation', () => { it('should open a URL', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: { url: 'https://example.com' }, })); const result = await adapter.open({ url: 'https://example.com' }); expect(result.success).toBe(true); expect(mockExecSync).toHaveBeenCalled(); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('open'); expect(callArgs).toContain('https://example.com'); }); it('should go back', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.back(); expect(result.success).toBe(true); expect(mockExecSync).toHaveBeenCalled(); }); it('should go forward', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.forward(); expect(result.success).toBe(true); }); it('should reload', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.reload(); expect(result.success).toBe(true); }); it('should close', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.close(); expect(result.success).toBe(true); }); }); describe('interaction', () => { it('should click an element', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: { clicked: true }, })); const result = await adapter.click({ target: '@e1' }); expect(result.success).toBe(true); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('click'); expect(callArgs).toContain('@e1'); }); it('should fill an input', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: { filled: true }, })); const result = await adapter.fill({ target: '@e1', value: 'test' }); expect(result.success).toBe(true); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('fill'); }); it('should type text', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.type({ target: '@e1', text: 'hello' }); expect(result.success).toBe(true); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('type'); }); it('should press a key', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.press('Enter'); expect(result.success).toBe(true); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('press'); expect(callArgs).toContain('Enter'); }); it('should hover an element', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.hover('@e1'); expect(result.success).toBe(true); }); it('should scroll', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.scroll('down', 500); expect(result.success).toBe(true); }); }); describe('information retrieval', () => { it('should get text', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: 'Element text', })); const result = await adapter.getText('@e1'); expect(result.success).toBe(true); expect(result.data).toBe('Element text'); }); it('should get title', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: 'Page Title', })); const result = await adapter.getTitle(); expect(result.success).toBe(true); }); it('should get URL', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: 'https://example.com', })); const result = await adapter.getUrl(); expect(result.success).toBe(true); }); }); describe('state checks', () => { it('should check visibility', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: true, })); const result = await adapter.isVisible('@e1'); expect(result.success).toBe(true); expect(result.data).toBe(true); }); it('should check if enabled', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: true, })); const result = await adapter.isEnabled('@e1'); expect(result.success).toBe(true); }); }); describe('snapshot', () => { it('should take a snapshot', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: { tree: { role: 'document', children: [] }, refs: {}, url: 'https://example.com', title: 'Test', }, })); const result = await adapter.snapshot(); expect(result.success).toBe(true); expect(result.data).toBeDefined(); }); it('should take interactive snapshot', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: { tree: { role: 'document' } }, })); const result = await adapter.snapshot({ interactive: true }); expect(result.success).toBe(true); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('-i'); }); it('should take compact snapshot', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.snapshot({ compact: true }); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('-c'); }); }); describe('screenshot', () => { it('should take a screenshot', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true, data: 'base64encodedimage', })); const result = await adapter.screenshot(); expect(result.success).toBe(true); }); it('should take full page screenshot', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.screenshot({ fullPage: true }); const callArgs = (mockExecSync.mock.calls[0][1] as string[]).join(' '); expect(callArgs).toContain('--full'); }); }); describe('wait', () => { it('should wait for selector', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.wait({ selector: '#element' }); expect(result.success).toBe(true); }); it('should wait for timeout', async () => { mockExecSync.mockReturnValue(JSON.stringify({ success: true })); const result = await adapter.wait({ timeout: 1000 }); expect(result.success).toBe(true); }); }); describe('error handling', () => { it('should handle command failure', async () => { mockExecSync.mockImplementation(() => { throw new Error('Command failed'); }); const result = await adapter.open({ url: 'https://example.com' }); expect(result.success).toBe(false); expect(result.error).toContain('Command failed'); }); it('should handle invalid JSON response', async () => { mockExecSync.mockReturnValue('invalid json'); const result = await adapter.open({ url: 'https://example.com' }); // Should fall back to raw string expect(result.success).toBe(true); }); }); });