diff --git a/src/PageCollector.ts b/src/PageCollector.ts index cff2148d..a25805f9 100644 --- a/src/PageCollector.ts +++ b/src/PageCollector.ts @@ -16,6 +16,7 @@ import { } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js'; import {FakeIssuesManager} from './DevtoolsUtils.js'; +import {features} from './features.js'; import {logger} from './logger.js'; import type { CDPSession, @@ -231,6 +232,9 @@ export class ConsoleCollector extends PageCollector< override addPage(page: Page): void { super.addPage(page); + if (!features.issues) { + return; + } if (!this.#subscribedPages.has(page)) { const subscriber = new PageIssueSubscriber(page); this.#subscribedPages.set(page, subscriber); diff --git a/src/features.ts b/src/features.ts new file mode 100644 index 00000000..6260edc7 --- /dev/null +++ b/src/features.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +let issuesEnabled = false; + +export const features = { + get issues() { + return issuesEnabled; + }, +}; + +export function setIssuesEnabled(value: boolean) { + issuesEnabled = value; +} diff --git a/src/main.ts b/src/main.ts index 77956876..b517b373 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import './polyfill.js'; import type {Channel} from './browser.js'; import {ensureBrowserConnected, ensureBrowserLaunched} from './browser.js'; import {parseArguments} from './cli.js'; +import {features} from './features.js'; import {loadIssueDescriptions} from './issue-descriptions.js'; import {logger, saveLogsToFile} from './logger.js'; import {McpContext} from './McpContext.js'; @@ -190,7 +191,9 @@ for (const tool of tools) { registerTool(tool); } -await loadIssueDescriptions(); +if (features.issues) { + await loadIssueDescriptions(); +} const transport = new StdioServerTransport(); await server.connect(transport); logger('Chrome DevTools MCP Server connected'); diff --git a/src/tools/console.ts b/src/tools/console.ts index ee672018..f69f5d88 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {features} from '../features.js'; import {zod} from '../third_party/index.js'; import type {ConsoleMessageType} from '../third_party/index.js'; @@ -11,7 +12,7 @@ import {ToolCategory} from './categories.js'; import {defineTool} from './ToolDefinition.js'; type ConsoleResponseType = ConsoleMessageType | 'issue'; -const FILTERABLE_MESSAGE_TYPES: readonly [ +const FILTERABLE_MESSAGE_TYPES: [ ConsoleResponseType, ...ConsoleResponseType[], ] = [ @@ -37,6 +38,10 @@ const FILTERABLE_MESSAGE_TYPES: readonly [ 'issue', ]; +if (features.issues) { + FILTERABLE_MESSAGE_TYPES.push('issue'); +} + export const listConsoleMessages = defineTool({ name: 'list_console_messages', description: diff --git a/tests/PageCollector.test.ts b/tests/PageCollector.test.ts index 06438183..ef31d006 100644 --- a/tests/PageCollector.test.ts +++ b/tests/PageCollector.test.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import {beforeEach, describe, it} from 'node:test'; +import {afterEach, beforeEach, describe, it} from 'node:test'; import type { Browser, @@ -17,6 +17,7 @@ import type { import sinon from 'sinon'; import {AggregatedIssue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js'; +import {setIssuesEnabled} from '../src/features.js'; import type {ListenerMap} from '../src/PageCollector.js'; import { ConsoleCollector, @@ -357,6 +358,11 @@ describe('ConsoleCollector', () => { }, }, }; + setIssuesEnabled(true); + }); + + afterEach(() => { + setIssuesEnabled(false); }); it('emits issues on page', async () => { diff --git a/tests/tools/console.test.ts b/tests/tools/console.test.ts index 770ccb4f..73c6f092 100644 --- a/tests/tools/console.test.ts +++ b/tests/tools/console.test.ts @@ -4,8 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; -import {before, describe, it} from 'node:test'; +import {afterEach, before, beforeEach, describe, it} from 'node:test'; +import {setIssuesEnabled} from '../../src/features.js'; import {loadIssueDescriptions} from '../../src/issue-descriptions.js'; import { getConsoleMessage, @@ -41,40 +42,37 @@ describe('console', () => { }); }); - it('lists issues', async () => { + it('work with primitive unhandled errors', async () => { await withBrowser(async (response, context) => { const page = await context.newPage(); - const issuePromise = new Promise(resolve => { - page.once('issue', () => { - resolve(); - }); - }); - await page.setContent(''); - await issuePromise; + await page.setContent(''); await listConsoleMessages.handler({params: {}}, response, context); const formattedResponse = await response.handle('test', context); const textContent = formattedResponse[0] as {text: string}; assert.ok( - textContent.text.includes( - `msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`, - ), + textContent.text.includes('msgid=1 [error] undefined (0 args)'), ); }); }); - it('lists issues after a page reload', async () => { - await withBrowser(async (response, context) => { - const page = await context.newPage(); - const issuePromise = new Promise(resolve => { - page.once('issue', () => { - resolve(); + describe('issues', () => { + beforeEach(() => { + setIssuesEnabled(true); + }); + afterEach(() => { + setIssuesEnabled(false); + }); + it('lists issues', async () => { + await withBrowser(async (response, context) => { + const page = await context.newPage(); + const issuePromise = new Promise(resolve => { + page.once('issue', () => { + resolve(); + }); }); - }); - - await page.setContent(''); - await issuePromise; - await listConsoleMessages.handler({params: {}}, response, context); - { + await page.setContent(''); + await issuePromise; + await listConsoleMessages.handler({params: {}}, response, context); const formattedResponse = await response.handle('test', context); const textContent = formattedResponse[0] as {text: string}; assert.ok( @@ -82,38 +80,49 @@ describe('console', () => { `msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`, ), ); - } - - const anotherIssuePromise = new Promise(resolve => { - page.once('issue', () => { - resolve(); - }); }); - await page.reload(); - await page.setContent(''); - await anotherIssuePromise; - { - const formattedResponse = await response.handle('test', context); - const textContent = formattedResponse[0] as {text: string}; - assert.ok( - textContent.text.includes( - `msgid=2 [issue] An element doesn't have an autocomplete attribute (count: 1)`, - ), - ); - } }); - }); - it('work with primitive unhandled errors', async () => { - await withBrowser(async (response, context) => { - const page = await context.newPage(); - await page.setContent(''); - await listConsoleMessages.handler({params: {}}, response, context); - const formattedResponse = await response.handle('test', context); - const textContent = formattedResponse[0] as {text: string}; - assert.ok( - textContent.text.includes('msgid=1 [error] undefined (0 args)'), - ); + it('lists issues after a page reload', async () => { + await withBrowser(async (response, context) => { + const page = await context.newPage(); + const issuePromise = new Promise(resolve => { + page.once('issue', () => { + resolve(); + }); + }); + + await page.setContent(''); + await issuePromise; + await listConsoleMessages.handler({params: {}}, response, context); + { + const formattedResponse = await response.handle('test', context); + const textContent = formattedResponse[0] as {text: string}; + assert.ok( + textContent.text.includes( + `msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`, + ), + ); + } + + const anotherIssuePromise = new Promise(resolve => { + page.once('issue', () => { + resolve(); + }); + }); + await page.reload(); + await page.setContent(''); + await anotherIssuePromise; + { + const formattedResponse = await response.handle('test', context); + const textContent = formattedResponse[0] as {text: string}; + assert.ok( + textContent.text.includes( + `msgid=2 [issue] An element doesn't have an autocomplete attribute (count: 1)`, + ), + ); + } + }); }); }); });