cdp.test.ts
1 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 2 3 function createChromeMock() { 4 const debuggerEventListeners: Array<(source: { tabId?: number }, method: string, params: any) => void> = []; 5 const tabRemovedListeners: Array<(tabId: number) => void> = []; 6 const tabs = { 7 get: vi.fn(async (_tabId: number) => ({ 8 id: 1, 9 windowId: 1, 10 url: 'https://x.com/home', 11 })), 12 onRemoved: { addListener: vi.fn((fn: (tabId: number) => void) => { tabRemovedListeners.push(fn); }) }, 13 onUpdated: { addListener: vi.fn() }, 14 }; 15 16 const debuggerApi = { 17 attach: vi.fn(async () => {}), 18 detach: vi.fn(async () => {}), 19 sendCommand: vi.fn(async (_target: unknown, method: string) => { 20 if (method === 'Runtime.evaluate') return { result: { value: 'ok' } }; 21 return {}; 22 }), 23 onDetach: { addListener: vi.fn() }, 24 onEvent: { addListener: vi.fn((fn: (source: { tabId?: number }, method: string, params: any) => void) => { debuggerEventListeners.push(fn); }) }, 25 }; 26 27 const scripting = { 28 executeScript: vi.fn(async () => [{ result: { removed: 1 } }]), 29 }; 30 31 return { 32 chrome: { 33 tabs, 34 debugger: debuggerApi, 35 scripting, 36 runtime: { id: 'opencli-test' }, 37 }, 38 debuggerApi, 39 scripting, 40 debuggerEventListeners, 41 tabRemovedListeners, 42 }; 43 } 44 45 describe('cdp attach recovery', () => { 46 beforeEach(() => { 47 vi.resetModules(); 48 }); 49 50 afterEach(() => { 51 vi.unstubAllGlobals(); 52 }); 53 54 it('does not mutate the DOM before a successful attach', async () => { 55 const { chrome, debuggerApi, scripting } = createChromeMock(); 56 vi.stubGlobal('chrome', chrome); 57 58 const mod = await import('./cdp'); 59 const result = await mod.evaluate(1, '1'); 60 61 expect(result).toBe('ok'); 62 expect(debuggerApi.attach).toHaveBeenCalledTimes(1); 63 expect(scripting.executeScript).not.toHaveBeenCalled(); 64 }); 65 66 it('uses the default execution context for a frame when isolated worlds also exist', async () => { 67 const { chrome, debuggerApi, debuggerEventListeners } = createChromeMock(); 68 vi.stubGlobal('chrome', chrome); 69 70 const mod = await import('./cdp'); 71 mod.registerFrameTracking(); 72 73 expect(debuggerEventListeners).toHaveLength(1); 74 debuggerEventListeners[0]( 75 { tabId: 1 }, 76 'Runtime.executionContextCreated', 77 { context: { id: 11, auxData: { frameId: 'frame-1', isDefault: false } } }, 78 ); 79 debuggerEventListeners[0]( 80 { tabId: 1 }, 81 'Runtime.executionContextCreated', 82 { context: { id: 22, auxData: { frameId: 'frame-1', isDefault: true } } }, 83 ); 84 85 await mod.evaluateInFrame(1, 'document.title', 'frame-1'); 86 87 expect(debuggerApi.sendCommand).toHaveBeenCalledWith( 88 { tabId: 1 }, 89 'Runtime.evaluate', 90 expect.objectContaining({ contextId: 22 }), 91 ); 92 }); 93 94 // Dead test: chrome.scripting.executeScript was removed from cdp.ts; 95 // this test references functionality that no longer exists. Delete or rewrite 96 // when cdp attach-recovery logic is next updated. 97 it.skip('retries after cleanup when attach fails with a foreign extension error', async () => { 98 const { chrome, debuggerApi, scripting } = createChromeMock(); 99 debuggerApi.attach 100 .mockRejectedValueOnce(new Error('Cannot access a chrome-extension:// URL of different extension')) 101 .mockResolvedValueOnce(undefined); 102 vi.stubGlobal('chrome', chrome); 103 104 const mod = await import('./cdp'); 105 const result = await mod.evaluate(1, '1'); 106 107 expect(result).toBe('ok'); 108 expect(scripting.executeScript).toHaveBeenCalledTimes(1); 109 expect(debuggerApi.attach).toHaveBeenCalledTimes(2); 110 }); 111 });