import { describe, expect, it } from 'bun:test';
import { runMcpRequests, getResponseText } from './browser-mcp-test-harness';
import type { MockPageState } from './browser-mcp-test-harness';

describe('ccs-browser MCP server - session and interception', () => {
  it('lists browser tools including navigate, click, type, and screenshot', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Example Page', currentUrl: 'https://example.com/' }],
      [{ jsonrpc: '2.0', id: 2, method: 'tools/list' }]
    );

    const tools = (
      responses.find((message) => message.id === 2)?.result as {
        tools: Array<{
          name: string;
          description?: string;
          inputSchema?: { properties?: Record<string, { type?: string; minimum?: number }> };
        }>;
      }
    ).tools;

    expect(tools.map((tool) => tool.name)).toEqual([
      'browser_get_session_info',
      'browser_get_url_and_title',
      'browser_get_visible_text',
      'browser_get_dom_snapshot',
      'browser_navigate',
      'browser_click',
      'browser_type',
      'browser_press_key',
      'browser_scroll',
      'browser_select_page',
      'browser_open_page',
      'browser_close_page',
      'browser_add_intercept_rule',
      'browser_remove_intercept_rule',
      'browser_list_intercept_rules',
      'browser_list_requests',
      'browser_set_download_behavior',
      'browser_list_downloads',
      'browser_cancel_download',
      'browser_set_file_input',
      'browser_drag_files',
      'browser_drag_element',
      'browser_pointer_action',
      'browser_start_recording',
      'browser_stop_recording',
      'browser_get_recording',
      'browser_clear_recording',
      'browser_start_replay',
      'browser_get_replay',
      'browser_cancel_replay',
      'browser_start_orchestration',
      'browser_get_orchestration',
      'browser_cancel_orchestration',
      'browser_export_artifact',
      'browser_import_artifact',
      'browser_list_artifacts',
      'browser_delete_artifact',
      'browser_take_screenshot',
      'browser_wait_for',
      'browser_eval',
      'browser_hover',
      'browser_query',
      'browser_take_element_screenshot',
      'browser_wait_for_event',
    ]);

    const clickTool = tools.find((tool) => tool.name === 'browser_click');
    expect(clickTool?.description).toContain('mouse event chain');
    expect(clickTool?.description).not.toContain('synthetic element.click()');
    expect(clickTool?.inputSchema?.properties?.offsetX).toMatchObject({ type: 'number' });
    expect(clickTool?.inputSchema?.properties?.offsetY).toMatchObject({ type: 'number' });
    expect(clickTool?.inputSchema?.properties?.button).toMatchObject({ type: 'string' });
    expect(clickTool?.inputSchema?.properties?.clickCount).toMatchObject({
      type: 'integer',
      minimum: 1,
    });

    const keyTool = tools.find((tool) => tool.name === 'browser_press_key');
    expect(keyTool?.inputSchema?.properties?.key).toMatchObject({ type: 'string' });
    expect(keyTool?.inputSchema?.properties?.modifiers).toMatchObject({ type: 'array' });

    const scrollTool = tools.find((tool) => tool.name === 'browser_scroll');
    expect(scrollTool?.inputSchema?.properties?.deltaX).toMatchObject({ type: 'number' });
    expect(scrollTool?.inputSchema?.properties?.deltaY).toMatchObject({ type: 'number' });

    const selectTool = tools.find((tool) => tool.name === 'browser_select_page');
    expect(selectTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(selectTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });

    const openTool = tools.find((tool) => tool.name === 'browser_open_page');
    expect(openTool?.inputSchema?.properties?.url).toMatchObject({ type: 'string' });

    const closeTool = tools.find((tool) => tool.name === 'browser_close_page');
    expect(closeTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(closeTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });

    const addRuleTool = tools.find((tool) => tool.name === 'browser_add_intercept_rule');
    expect(addRuleTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(addRuleTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.urlIncludes).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.method).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.resourceType).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.urlPattern).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.urlRegex).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.headerMatchers).toMatchObject({ type: 'array' });
    expect(addRuleTool?.inputSchema?.properties?.priority).toMatchObject({ type: 'integer' });
    expect(addRuleTool?.inputSchema?.properties?.action).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.statusCode).toMatchObject({ type: 'integer' });
    expect(addRuleTool?.inputSchema?.properties?.responseHeaders).toMatchObject({ type: 'array' });
    expect(addRuleTool?.inputSchema?.properties?.headers).toBeUndefined();
    expect(addRuleTool?.inputSchema?.properties?.body).toMatchObject({ type: 'string' });
    expect(addRuleTool?.inputSchema?.properties?.contentType).toMatchObject({ type: 'string' });

    const removeRuleTool = tools.find((tool) => tool.name === 'browser_remove_intercept_rule');
    expect(removeRuleTool?.inputSchema?.properties?.ruleId).toMatchObject({ type: 'string' });

    const listRulesTool = tools.find((tool) => tool.name === 'browser_list_intercept_rules');
    expect(listRulesTool?.inputSchema).toMatchObject({ type: 'object' });

    const listRequestsTool = tools.find((tool) => tool.name === 'browser_list_requests');
    expect(listRequestsTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(listRequestsTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });
    expect(listRequestsTool?.inputSchema?.properties?.limit).toMatchObject({ type: 'integer' });

    const setDownloadTool = tools.find((tool) => tool.name === 'browser_set_download_behavior');
    expect(setDownloadTool?.inputSchema?.properties?.behavior).toMatchObject({ type: 'string' });
    expect(setDownloadTool?.inputSchema?.properties?.downloadPath).toMatchObject({ type: 'string' });
    expect(setDownloadTool?.inputSchema?.properties?.eventsEnabled).toMatchObject({ type: 'boolean' });

    const listDownloadsTool = tools.find((tool) => tool.name === 'browser_list_downloads');
    expect(listDownloadsTool?.inputSchema?.properties?.limit).toMatchObject({ type: 'integer' });
    expect(listDownloadsTool?.inputSchema?.properties?.pageId).toBeUndefined();

    const cancelDownloadTool = tools.find((tool) => tool.name === 'browser_cancel_download');
    expect(cancelDownloadTool?.inputSchema?.properties?.downloadId).toMatchObject({ type: 'string' });
    expect(cancelDownloadTool?.inputSchema?.properties?.guid).toMatchObject({ type: 'string' });

    const uploadTool = tools.find((tool) => tool.name === 'browser_set_file_input');
    expect(uploadTool?.inputSchema?.properties?.selector).toMatchObject({ type: 'string' });
    expect(uploadTool?.inputSchema?.properties?.files).toMatchObject({ type: 'array' });
    expect(uploadTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(uploadTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });
    expect(uploadTool?.inputSchema?.properties?.nth).toMatchObject({ type: 'integer' });
    expect(uploadTool?.inputSchema?.properties?.frameSelector).toMatchObject({ type: 'string' });
    expect(uploadTool?.inputSchema?.properties?.pierceShadow).toMatchObject({ type: 'boolean' });

    const dragFilesTool = tools.find((tool) => tool.name === 'browser_drag_files');
    expect(dragFilesTool?.inputSchema?.properties?.selector).toMatchObject({ type: 'string' });
    expect(dragFilesTool?.inputSchema?.properties?.files).toMatchObject({ type: 'array' });
    expect(dragFilesTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(dragFilesTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });
    expect(dragFilesTool?.inputSchema?.properties?.nth).toMatchObject({ type: 'integer' });
    expect(dragFilesTool?.inputSchema?.properties?.frameSelector).toMatchObject({ type: 'string' });
    expect(dragFilesTool?.inputSchema?.properties?.pierceShadow).toMatchObject({ type: 'boolean' });

    const dragElementTool = tools.find((tool) => tool.name === 'browser_drag_element');
    expect(dragElementTool?.inputSchema?.properties?.selector).toMatchObject({ type: 'string' });
    expect(dragElementTool?.inputSchema?.properties?.targetSelector).toMatchObject({ type: 'string' });
    expect(dragElementTool?.inputSchema?.properties?.targetX).toMatchObject({ type: 'number' });
    expect(dragElementTool?.inputSchema?.properties?.targetY).toMatchObject({ type: 'number' });
    expect(dragElementTool?.inputSchema?.properties?.steps).toMatchObject({ type: 'integer' });

    const pointerTool = tools.find((tool) => tool.name === 'browser_pointer_action');
    expect(pointerTool?.inputSchema?.properties?.actions).toMatchObject({ type: 'array' });

    const startRecordingTool = tools.find((tool) => tool.name === 'browser_start_recording');
    expect(startRecordingTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(startRecordingTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });

    const stopRecordingTool = tools.find((tool) => tool.name === 'browser_stop_recording');
    expect(stopRecordingTool?.inputSchema).toMatchObject({ type: 'object' });

    const getRecordingTool = tools.find((tool) => tool.name === 'browser_get_recording');
    expect(getRecordingTool?.inputSchema).toMatchObject({ type: 'object' });

    const clearRecordingTool = tools.find((tool) => tool.name === 'browser_clear_recording');
    expect(clearRecordingTool?.inputSchema).toMatchObject({ type: 'object' });

    const startReplayTool = tools.find((tool) => tool.name === 'browser_start_replay');
    expect(startReplayTool?.inputSchema?.properties?.steps).toMatchObject({ type: 'array' });
    expect(startReplayTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(startReplayTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });

    const getReplayTool = tools.find((tool) => tool.name === 'browser_get_replay');
    expect(getReplayTool?.inputSchema).toMatchObject({ type: 'object' });

    const cancelReplayTool = tools.find((tool) => tool.name === 'browser_cancel_replay');
    expect(cancelReplayTool?.inputSchema).toMatchObject({ type: 'object' });

    const startOrchestrationTool = tools.find((tool) => tool.name === 'browser_start_orchestration');
    expect(startOrchestrationTool?.inputSchema?.properties?.blocks).toMatchObject({ type: 'array' });
    expect(startOrchestrationTool?.inputSchema?.properties?.pageIndex).toMatchObject({ type: 'integer' });
    expect(startOrchestrationTool?.inputSchema?.properties?.pageId).toMatchObject({ type: 'string' });

    const getOrchestrationTool = tools.find((tool) => tool.name === 'browser_get_orchestration');
    expect(getOrchestrationTool?.inputSchema).toMatchObject({ type: 'object' });

    const cancelOrchestrationTool = tools.find((tool) => tool.name === 'browser_cancel_orchestration');
    expect(cancelOrchestrationTool?.inputSchema).toMatchObject({ type: 'object' });

    const exportArtifactTool = tools.find((tool) => tool.name === 'browser_export_artifact');
    expect(exportArtifactTool?.inputSchema?.properties?.kind).toMatchObject({ type: 'string' });
    expect(exportArtifactTool?.inputSchema?.properties?.name).toMatchObject({ type: 'string' });

    const importArtifactTool = tools.find((tool) => tool.name === 'browser_import_artifact');
    expect(importArtifactTool?.inputSchema?.properties?.path).toMatchObject({ type: 'string' });

    const listArtifactsTool = tools.find((tool) => tool.name === 'browser_list_artifacts');
    expect(listArtifactsTool?.inputSchema).toMatchObject({ type: 'object' });

    const deleteArtifactTool = tools.find((tool) => tool.name === 'browser_delete_artifact');
    expect(deleteArtifactTool?.inputSchema?.properties?.name).toMatchObject({ type: 'string' });

    const queryTool = tools.find((tool) => tool.name === 'browser_query');
    expect(queryTool?.inputSchema?.properties?.fields).toMatchObject({
      type: 'array',
    });

    for (const tool of tools.filter((candidate) => candidate.inputSchema?.properties?.pageIndex)) {
      expect(tool.inputSchema?.properties?.pageIndex).toMatchObject({
        type: 'integer',
        minimum: 0,
      });
    }
  });

  it('marks the selected page in browser_get_session_info', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        { id: 'page-2', title: 'Docs', currentUrl: 'https://example.com/docs' },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 801,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 802,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const text = getResponseText(responses.find((message) => message.id === 802));
    expect(text).toContain('selected: true');
    expect(text).toContain('1. Docs');
  });

  it('uses the selected page when pageIndex is omitted', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Home',
          currentUrl: 'https://example.com/',
          visibleText: 'Home text',
        },
        {
          id: 'page-2',
          title: 'Docs',
          currentUrl: 'https://example.com/docs',
          visibleText: 'Docs text',
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 811,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 812,
          method: 'tools/call',
          params: { name: 'browser_get_visible_text', arguments: {} },
        },
      ]
    );

    const text = getResponseText(responses.find((message) => message.id === 812));
    expect(text).toContain('Docs text');
  });

  it('opens a page and makes it selected', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 821,
          method: 'tools/call',
          params: {
            name: 'browser_open_page',
            arguments: { url: 'https://example.com/new' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 822,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const openText = getResponseText(responses.find((message) => message.id === 821));
    expect(openText).toContain('status: opened');
    expect(openText).toContain('url: https://example.com/new');

    const listText = getResponseText(responses.find((message) => message.id === 822));
    expect(listText).toContain('https://example.com/new');
    expect(listText).toContain('selected: true');
  });

  it('closes the selected page and falls back deterministically', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        { id: 'page-2', title: 'Docs', currentUrl: 'https://example.com/docs' },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 831,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 832,
          method: 'tools/call',
          params: { name: 'browser_close_page', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 833,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const closeText = getResponseText(responses.find((message) => message.id === 832));
    expect(closeText).toContain('status: closed');

    const listText = getResponseText(responses.find((message) => message.id === 833));
    expect(listText).toContain('0. Home');
    expect(listText).toContain('selected: true');
    expect(listText).not.toContain('Docs');
  });

  it('keeps the selected page when closing a different page', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Home',
          currentUrl: 'https://example.com/',
          visibleText: 'Home text',
        },
        {
          id: 'page-2',
          title: 'Docs',
          currentUrl: 'https://example.com/docs',
          visibleText: 'Docs text',
        },
        {
          id: 'page-3',
          title: 'Pricing',
          currentUrl: 'https://example.com/pricing',
          visibleText: 'Pricing text',
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 834,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 0 } },
        },
        {
          jsonrpc: '2.0',
          id: 835,
          method: 'tools/call',
          params: { name: 'browser_close_page', arguments: { pageId: 'page-3' } },
        },
        {
          jsonrpc: '2.0',
          id: 836,
          method: 'tools/call',
          params: { name: 'browser_get_visible_text', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 837,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const visibleText = getResponseText(responses.find((message) => message.id === 836));
    expect(visibleText).toContain('Home text');

    const listText = getResponseText(responses.find((message) => message.id === 837));
    expect(listText).toContain('0. Home');
    expect(listText).toContain('selected: true');
    expect(listText).not.toContain('Pricing');
  });

  it('reconciles a stale selected page when browser_get_session_info is called', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 841,
          method: 'tools/call',
          params: { name: 'browser_open_page', arguments: { url: 'https://example.com/new' } },
        },
        {
          jsonrpc: '2.0',
          id: 842,
          method: 'tools/call',
          params: { name: 'browser_close_page', arguments: { pageId: 'page-2' } },
        },
        {
          jsonrpc: '2.0',
          id: 843,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 843));
    expect(listText).toContain('0. Home');
    expect(listText).toContain('selected: true');
  });

  it('closes a page by pageId', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 851,
          method: 'tools/call',
          params: { name: 'browser_open_page', arguments: { url: 'https://example.com/new' } },
        },
        {
          jsonrpc: '2.0',
          id: 852,
          method: 'tools/call',
          params: { name: 'browser_close_page', arguments: { pageId: 'page-2' } },
        },
        {
          jsonrpc: '2.0',
          id: 853,
          method: 'tools/call',
          params: { name: 'browser_get_session_info', arguments: {} },
        },
      ]
    );

    const closeText = getResponseText(responses.find((message) => message.id === 852));
    expect(closeText).toContain('pageId: page-2');
    expect(closeText).toContain('status: closed');

    const listText = getResponseText(responses.find((message) => message.id === 853));
    expect(listText).toContain('0. Home');
    expect(listText).not.toContain('https://example.com/new');
  });

  it('adds an interception rule and lists it by bound pageId', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        { id: 'page-2', title: 'Docs', currentUrl: 'https://example.com/docs' },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 901,
          method: 'tools/call',
          params: {
            name: 'browser_select_page',
            arguments: { pageIndex: 1 },
          },
        },
        {
          jsonrpc: '2.0',
          id: 902,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'GET', action: 'continue' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 903,
          method: 'tools/call',
          params: {
            name: 'browser_list_intercept_rules',
            arguments: {},
          },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 903));
    expect(listText).toContain('pageId: page-2');
    expect(listText).toContain('urlIncludes: /api');
    expect(listText).toContain('method: GET');
    expect(listText).toContain('action: continue');
  });

  it('keeps an existing rule bound to the original page after selected page changes', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        { id: 'page-2', title: 'Docs', currentUrl: 'https://example.com/docs' },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 911,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 0 } },
        },
        {
          jsonrpc: '2.0',
          id: 912,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'POST', action: 'fail' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 913,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 914,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 914));
    expect(listText).toContain('pageId: page-1');
    expect(listText).toContain('method: POST');
    expect(listText).toContain('action: fail');
  });

  it('keeps richer matching rules bound to the original page after selected page changes', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        { id: 'page-2', title: 'Docs', currentUrl: 'https://example.com/docs' },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 1003,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 1004,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              resourceType: 'XHR',
              priority: 7,
              action: 'continue',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 1005,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 0 } },
        },
        {
          jsonrpc: '2.0',
          id: 1006,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 1006));
    expect(listText).toContain('pageId: page-2');
    expect(listText).toContain('resourceType: XHR');
    expect(listText).toContain('priority: 7');
  });

  it('removes an interception rule by ruleId', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 921,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'GET', action: 'continue' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 922,
          method: 'tools/call',
          params: {
            name: 'browser_remove_intercept_rule',
            arguments: { ruleId: 'rule-1' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 923,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ]
    );

    const removeText = getResponseText(responses.find((message) => message.id === 922));
    expect(removeText).toContain('ruleId: rule-1');
    expect(removeText).toContain('status: removed');

    const listText = getResponseText(responses.find((message) => message.id === 923));
    expect(listText).toBe('status: empty');
  });

  it('records recent requests with matched rule action summaries', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Home',
          currentUrl: 'https://example.com/',
          intercept: {
            pausedRequests: [
              {
                requestId: 'req-1',
                url: 'https://example.com/api/users',
                method: 'GET',
                resourceType: 'XHR',
              },
              {
                requestId: 'req-2',
                url: 'https://example.com/assets/app.js',
                method: 'GET',
                resourceType: 'Script',
              },
            ],
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 931,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'GET', action: 'fail' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 932,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 932));
    expect(listText).toContain('requestId: req-1');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(listText).toContain('action: fail');
    expect(listText).toContain('requestId: req-2');
    expect(listText).toContain('action: continue');
  });

  it('removes rules and recent requests bound to a page after that page is closed', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        {
          id: 'page-2',
          title: 'Docs',
          currentUrl: 'https://example.com/docs',
          intercept: {
            pausedRequests: [{ requestId: 'req-closed', url: 'https://example.com/api/docs', method: 'GET' }],
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 941,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 942,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'GET', action: 'continue' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 943,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 944,
          method: 'tools/call',
          params: { name: 'browser_close_page', arguments: { pageId: 'page-2' } },
        },
        {
          jsonrpc: '2.0',
          id: 945,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 946,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const preCloseRequestsText = getResponseText(responses.find((message) => message.id === 943));
    expect(preCloseRequestsText).toContain('requestId: req-closed');

    const listRulesText = getResponseText(responses.find((message) => message.id === 945));
    expect(listRulesText).not.toContain('pageId: page-2');

    const postCloseRequestsText = getResponseText(responses.find((message) => message.id === 946));
    expect(postCloseRequestsText).not.toContain('requestId: req-closed');
    expect(postCloseRequestsText).not.toContain('pageId: page-2');
  });

  it('rejects browser_add_intercept_rule when pageIndex and pageId are both provided', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 951,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              pageIndex: 0,
              pageId: 'page-1',
              urlIncludes: '/api',
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 951);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: pageIndex and pageId cannot be used together');
  });

  it('rejects browser_add_intercept_rule when action is invalid', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 952,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api',
              action: 'mock',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 952);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: action must be one of: continue, fail');
  });

  it('rejects intercept rules when urlPattern and urlRegex are both provided', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 981,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlPattern: 'https://example.com/api/*',
              urlRegex: '^https://example\\.com/api/',
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 981);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: urlPattern and urlRegex cannot be used together');
  });

  it('rejects intercept rules when priority is not an integer', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 982,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api',
              priority: 1.5,
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 982);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: priority must be an integer');
  });

  it('rejects intercept rules when headerMatchers is not an array', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 983,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: 'x',
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 983);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: headerMatchers must be an array');
  });

  it('rejects intercept rules when a header matcher is missing name', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 984,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: [{ valueIncludes: 'staging' }],
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 984);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: headerMatchers.name is required');
  });

  it('rejects intercept rules when a header matcher has no value matcher', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 985,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: [{ name: 'x-env' }],
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 985);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: headerMatchers entry must include valueIncludes or valueRegex');
  });

  it('rejects intercept rules when no matching condition is provided', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 986,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              priority: 10,
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 986);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: at least one matching condition is required');
  });

  it('adds a resourceType interception rule and lists its richer matching summary', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 987,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              resourceType: 'XHR',
              priority: 10,
              action: 'continue',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 988,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 988));
    expect(listText).toContain('resourceType: XHR');
    expect(listText).toContain('priority: 10');
  });

  it('prefers the higher-priority matched rule over an earlier lower-priority rule', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-priority',
              url: 'https://example.com/api/orders',
              method: 'GET',
              resourceType: 'XHR',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 989,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api',
              action: 'fulfill',
              statusCode: 201,
              body: 'low',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 990,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              resourceType: 'XHR',
              priority: 10,
              action: 'fulfill',
              statusCode: 202,
              body: 'high',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 991,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 991));
    expect(listText).toContain('requestId: req-priority');
    expect(listText).toContain('matchedRuleId: rule-2');
    expect(pages[0]?.intercept?.fulfilledRequests?.[0]?.responseCode).toBe(202);
  });

  it('keeps creation order when matched rules have the same priority', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-same-priority',
              url: 'https://example.com/api/orders',
              method: 'GET',
              resourceType: 'XHR',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 9911,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api',
              priority: 5,
              action: 'fulfill',
              statusCode: 201,
              body: 'first',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 9912,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              resourceType: 'XHR',
              priority: 5,
              action: 'fulfill',
              statusCode: 202,
              body: 'second',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 9913,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 9913));
    expect(listText).toContain('requestId: req-same-priority');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(pages[0]?.intercept?.fulfilledRequests?.[0]?.responseCode).toBe(201);
  });

  it('matches urlPattern rules with wildcard syntax', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-pattern',
              url: 'https://example.com/api/v1/users',
              method: 'GET',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 992,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlPattern: 'https://example.com/api/*',
              action: 'fail',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 993,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 993));
    expect(listText).toContain('requestId: req-pattern');
    expect(listText).toContain('action: fail');
    expect(pages[0]?.intercept?.failedRequests?.[0]?.requestId).toBe('req-pattern');
  });

  it('matches urlRegex rules', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-regex',
              url: 'https://example.com/api/users',
              method: 'GET',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 994,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlRegex: '^https://example\\.com/api/(users|teams)$',
              action: 'continue',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 995,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 995));
    expect(listText).toContain('requestId: req-regex');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(pages[0]?.intercept?.continuedRequestIds).toContain('req-regex');
  });

  it('matches headerMatchers using valueIncludes and case-insensitive header names', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-header-includes',
              url: 'https://example.com/api/header-includes',
              method: 'GET',
              requestHeaders: {
                'X-Env': 'staging-us',
              },
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 996,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: [{ name: 'x-env', valueIncludes: 'staging' }],
              action: 'fulfill',
              statusCode: 207,
              body: 'matched-includes',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 997,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 997));
    expect(listText).toContain('requestId: req-header-includes');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(pages[0]?.intercept?.fulfilledRequests?.[0]?.responseCode).toBe(207);
  });

  it('matches headerMatchers using valueRegex', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-header-regex',
              url: 'https://example.com/api/header-regex',
              method: 'GET',
              requestHeaders: {
                'x-tenant': 'acme-prod',
              },
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 998,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: [{ name: 'X-Tenant', valueRegex: '^acme-' }],
              action: 'continue',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 999,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 999));
    expect(listText).toContain('requestId: req-header-regex');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(pages[0]?.intercept?.continuedRequestIds).toContain('req-header-regex');
  });

  it('adds a fulfill interception rule and lists its response summary', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 961,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              method: 'GET',
              action: 'fulfill',
              statusCode: 202,
              contentType: 'application/json',
              body: '{"ok":true}',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 962,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ]
    );

    const listText = getResponseText(responses.find((message) => message.id === 962));
    expect(listText).toContain('action: fulfill');
    expect(listText).toContain('statusCode: 202');
    expect(listText).toContain('contentType: application/json');
  });

  it('fulfills a paused request with the configured mock response', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-fulfill-1',
              url: 'https://example.com/api/mock/users',
              method: 'GET',
              resourceType: 'XHR',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 963,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              method: 'GET',
              action: 'fulfill',
              statusCode: 200,
              contentType: 'application/json',
              body: '{"users":[1]}',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 964,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 964));
    expect(listText).toContain('requestId: req-fulfill-1');
    expect(listText).toContain('matchedRuleId: rule-1');
    expect(listText).toContain('action: fulfill');
    expect(pages[0]?.intercept?.fulfilledRequests).toEqual([
      expect.objectContaining({
        requestId: 'req-fulfill-1',
        responseCode: 200,
      }),
    ]);
  });

  it('passes custom response headers to Fetch.fulfillRequest', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Home',
        currentUrl: 'https://example.com/',
        intercept: {
          pausedRequests: [
            {
              requestId: 'req-fulfill-2',
              url: 'https://example.com/api/mock/headers',
              method: 'GET',
            },
          ],
        },
      },
    ];
    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 965,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock/headers',
              action: 'fulfill',
              statusCode: 201,
              responseHeaders: [
                { name: 'Cache-Control', value: 'no-store' },
                { name: 'X-Mocked-By', value: 'ccs-browser' },
              ],
              body: 'ok',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 966,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 966));
    expect(listText).toContain('action: fulfill');
    expect(pages[0]?.intercept?.fulfilledRequests?.[0]?.responseHeaders).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: 'Cache-Control', value: 'no-store' }),
        expect.objectContaining({ name: 'X-Mocked-By', value: 'ccs-browser' }),
      ])
    );
  });

  it('allows fulfill rules with an empty response body', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 967,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/empty',
              action: 'fulfill',
              statusCode: 204,
              contentType: 'text/plain',
              body: '',
            },
          },
        },
      ]
    );

    const addText = getResponseText(responses.find((message) => message.id === 967));
    expect(addText).toContain('action: fulfill');
    expect(addText).toContain('statusCode: 204');
  });

  it('rejects fulfill rules when statusCode is out of range', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 968,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              action: 'fulfill',
              statusCode: 99,
              body: 'x',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 968);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: statusCode must be an integer between 100 and 599');
  });

  it('rejects fulfill rules when responseHeaders is not an array', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 969,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              action: 'fulfill',
              responseHeaders: 'x',
              body: 'x',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 969);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: responseHeaders must be an array');
  });

  it('rejects fulfill rules when a responseHeaders entry is missing name', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 970,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              action: 'fulfill',
              responseHeaders: [{ value: 'x' }],
              body: 'x',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 970);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: responseHeaders.name is required');
  });

  it('rejects fulfill rules when body is not a string', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 971,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock',
              action: 'fulfill',
              body: 123,
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 971);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: body must be a string');
  });

  it('rejects intercept rules when urlRegex is invalid', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 1001,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlRegex: '[',
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 1001);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: urlRegex must be a valid regular expression');
  });

  it('rejects intercept rules when headerMatchers.valueRegex is invalid', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
      [
        {
          jsonrpc: '2.0',
          id: 1002,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              headerMatchers: [{ name: 'x-env', valueRegex: '[' }],
              action: 'continue',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 1002);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain('Browser MCP failed: headerMatchers.valueRegex must be a valid regular expression');
  });

  it('removes fulfill rules and request summaries after the bound page is closed', async () => {
    const responses = await runMcpRequests(
      [
        { id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' },
        {
          id: 'page-2',
          title: 'Mocked',
          currentUrl: 'https://example.com/mocked',
          intercept: {
            pausedRequests: [
              { requestId: 'req-fulfill-close', url: 'https://example.com/api/mock/close', method: 'GET' },
            ],
          },
        },
      ],
      [
        { jsonrpc: '2.0', id: 972, method: 'tools/call', params: { name: 'browser_select_page', arguments: { pageIndex: 1 } } },
        {
          jsonrpc: '2.0',
          id: 973,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: {
              urlIncludes: '/api/mock/close',
              action: 'fulfill',
              statusCode: 200,
              body: 'done',
            },
          },
        },
        { jsonrpc: '2.0', id: 974, method: 'tools/call', params: { name: 'browser_list_requests', arguments: {} } },
        { jsonrpc: '2.0', id: 975, method: 'tools/call', params: { name: 'browser_close_page', arguments: { pageId: 'page-2' } } },
        { jsonrpc: '2.0', id: 976, method: 'tools/call', params: { name: 'browser_list_intercept_rules', arguments: {} } },
        { jsonrpc: '2.0', id: 977, method: 'tools/call', params: { name: 'browser_list_requests', arguments: {} } },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    expect(getResponseText(responses.find((message) => message.id === 974))).toContain('requestId: req-fulfill-close');
    expect(getResponseText(responses.find((message) => message.id === 976))).not.toContain('pageId: page-2');
    expect(getResponseText(responses.find((message) => message.id === 977))).not.toContain('requestId: req-fulfill-close');
  });

  it('returns only the requested number of recent requests', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Home',
          currentUrl: 'https://example.com/',
          intercept: {
            pausedRequests: [
              { requestId: 'req-1', url: 'https://example.com/api/one', method: 'GET', resourceType: 'XHR' },
              { requestId: 'req-2', url: 'https://example.com/api/two', method: 'GET', resourceType: 'XHR' },
            ],
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 953,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', method: 'GET', action: 'continue' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 954,
          method: 'tools/call',
          params: { name: 'browser_list_requests', arguments: { limit: 1 } },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const listText = getResponseText(responses.find((message) => message.id === 954));
    expect(listText).toContain('requestId: req-2');
    expect(listText).not.toContain('requestId: req-1');
  });

  it('fails browser_add_intercept_rule when Fetch.enable fails', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Home',
          currentUrl: 'https://example.com/',
          intercept: {
            enableError: 'Fetch.enable blocked',
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 955,
          method: 'tools/call',
          params: {
            name: 'browser_add_intercept_rule',
            arguments: { urlIncludes: '/api', action: 'continue' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 956,
          method: 'tools/call',
          params: { name: 'browser_list_intercept_rules', arguments: {} },
        },
      ],
      {
        responseTimeoutMs: 12000,
      }
    );

    const addResponse = responses.find((message) => message.id === 955);
    expect((addResponse?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(addResponse)).toContain('Browser MCP failed: Fetch.enable blocked');

    const listText = getResponseText(responses.find((message) => message.id === 956));
    expect(listText).toBe('status: empty');
  });

});
