Skip to content

Commit 5588163

Browse files
author
Natallia Harshunova
committed
Subscribe for Audits.issueAdded event
1 parent 016e2fd commit 5588163

File tree

9 files changed

+127
-17
lines changed

9 files changed

+127
-17
lines changed

scripts/post-build.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const i18n = {
6363
return str;
6464
},
6565
lockedLazyString: () => {},
66-
getLazilyComputedLocalizedString: () => {},
66+
getLazilyComputedLocalizedString: () => ()=>{},
6767
};
6868
6969
// TODO(jacktfranklin): once the DocumentLatency insight does not depend on
@@ -169,6 +169,19 @@ export const hostConfig = {};
169169
fs.copyFileSync(devtoolsLicenseFileSource, devtoolsLicenseFileDestination);
170170

171171
copyThirdPartyLicenseFiles();
172+
copyDevToolsDescriptionFiles();
173+
}
174+
175+
function copyDevToolsDescriptionFiles() {
176+
const sourceDir = path.join(
177+
process.cwd(),
178+
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions',
179+
);
180+
const destDir = path.join(
181+
BUILD_DIR,
182+
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions',
183+
);
184+
fs.cpSync(sourceDir, destDir, {recursive: true});
172185
}
173186

174187
main();

src/McpContext.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import fs from 'node:fs/promises';
77
import os from 'node:os';
88
import path from 'node:path';
99

10+
import {type Issue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
11+
1012
import {extractUrlLikeFromDevToolsTitle, urlsEqual} from './DevtoolsUtils.js';
1113
import type {ListenerMap} from './PageCollector.js';
1214
import {NetworkCollector, PageCollector} from './PageCollector.js';
@@ -29,6 +31,8 @@ import type {Context, DevToolsData} from './tools/ToolDefinition.js';
2931
import type {TraceResult} from './trace-processing/parse.js';
3032
import {WaitForHelper} from './WaitForHelper.js';
3133

34+
35+
3236
export interface TextSnapshotNode extends SerializedAXNode {
3337
id: string;
3438
backendNodeId?: number;
@@ -92,7 +96,7 @@ export class McpContext implements Context {
9296
// The most recent snapshot.
9397
#textSnapshot: TextSnapshot | null = null;
9498
#networkCollector: NetworkCollector;
95-
#consoleCollector: PageCollector<ConsoleMessage | Error>;
99+
#consoleCollector: PageCollector<ConsoleMessage | Error | Issue.Issue>;
96100

97101
#isRunningTrace = false;
98102
#networkConditionsMap = new WeakMap<Page, string>();
@@ -138,6 +142,9 @@ export class McpContext implements Context {
138142
collect(error);
139143
}
140144
},
145+
issue: issue => {
146+
collect(issue);
147+
},
141148
} as ListenerMap;
142149
},
143150
this.#options.experimentalIncludeAllPages,
@@ -205,16 +212,16 @@ export class McpContext implements Context {
205212

206213
getConsoleData(
207214
includePreservedMessages?: boolean,
208-
): Array<ConsoleMessage | Error> {
215+
): Array<ConsoleMessage | Error | Issue.Issue> {
209216
const page = this.getSelectedPage();
210217
return this.#consoleCollector.getData(page, includePreservedMessages);
211218
}
212219

213-
getConsoleMessageStableId(message: ConsoleMessage | Error): number {
220+
getConsoleMessageStableId(message: ConsoleMessage | Error | Issue.Issue): number {
214221
return this.#consoleCollector.getIdForResource(message);
215222
}
216223

217-
getConsoleMessageById(id: number): ConsoleMessage | Error {
224+
getConsoleMessageById(id: number): ConsoleMessage | Error | Issue.Issue {
218225
return this.#consoleCollector.getById(this.getSelectedPage(), id);
219226
}
220227

src/McpResponse.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright 2025 Google LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6+
import { Issue } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
7+
68
import type {ConsoleMessageData} from './formatters/consoleFormatter.js';
79
import {
810
formatConsoleEventShort,
@@ -16,6 +18,7 @@ import {
1618
getStatusFromRequest,
1719
} from './formatters/networkFormatter.js';
1820
import {formatSnapshotNode} from './formatters/snapshotFormatter.js';
21+
import {getIssueDescription} from './issue-descriptions.js';
1922
import type {McpContext} from './McpContext.js';
2023
import type {
2124
ConsoleMessage,
@@ -269,7 +272,7 @@ export class McpResponse implements Response {
269272
if ('type' in message) {
270273
return normalizedTypes.has(message.type());
271274
}
272-
return normalizedTypes.has('error');
275+
return normalizedTypes.has('error'); // TODO add filtering
273276
});
274277
}
275278

@@ -295,6 +298,18 @@ export class McpResponse implements Response {
295298
),
296299
};
297300
}
301+
if (item instanceof Issue.Issue) {
302+
const descriptionFile = item.getDescription()?.file;
303+
const description = descriptionFile
304+
? getIssueDescription(descriptionFile)
305+
: null;
306+
return {
307+
consoleMessageStableId,
308+
type: 'issue',
309+
message: item.primaryKey(),
310+
args: description ? [description] : [],
311+
};
312+
}
298313
return {
299314
consoleMessageStableId,
300315
type: 'error',

src/PageCollector.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import type * as CdpProtocol from '../node_modules/chrome-devtools-frontend/front_end/generated/protocol-proxy-api.js';
8+
import {IssuesManager, Issue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
9+
710
import {
811
type Browser,
912
type Frame,
1013
type Handler,
1114
type HTTPRequest,
1215
type Page,
13-
type PageEvents,
16+
type PageEvents as PuppeteerPageEvents,
1417
} from './third_party/index.js';
1518

19+
interface PageEvents extends PuppeteerPageEvents {
20+
issue: Issue.Issue;
21+
}
22+
1623
export type ListenerMap<EventMap extends PageEvents = PageEvents> = {
1724
[K in keyof EventMap]?: (event: EventMap[K]) => void;
1825
};
@@ -61,15 +68,15 @@ export class PageCollector<T> {
6168
async init() {
6269
const pages = await this.#browser.pages(this.#includeAllPages);
6370
for (const page of pages) {
64-
this.#initializePage(page);
71+
await this.addPage(page);
6572
}
6673

6774
this.#browser.on('targetcreated', async target => {
6875
const page = await target.page();
6976
if (!page) {
7077
return;
7178
}
72-
this.#initializePage(page);
79+
await this.addPage(page);
7380
});
7481
this.#browser.on('targetdestroyed', async target => {
7582
const page = await target.page();
@@ -80,15 +87,15 @@ export class PageCollector<T> {
8087
});
8188
}
8289

83-
public addPage(page: Page) {
84-
this.#initializePage(page);
85-
}
86-
87-
#initializePage(page: Page) {
90+
public async addPage(page: Page) {
8891
if (this.storage.has(page)) {
8992
return;
9093
}
94+
await this.#initializePage(page);
95+
}
9196

97+
async #initializePage(page: Page) {
98+
await this.subscribeForIssues(page);
9299
const idGenerator = createIdGenerator();
93100
const storedLists: Array<Array<WithSymbolId<T>>> = [[]];
94101
this.storage.set(page, storedLists);
@@ -116,6 +123,19 @@ export class PageCollector<T> {
116123
this.#listeners.set(page, listeners);
117124
}
118125

126+
protected async subscribeForIssues(page: Page) {
127+
const session = await page.createCDPSession();
128+
session.on('Audits.issueAdded',(data) => { // TODO unsubscribe
129+
// @ts-expect-error Types of protocol from Puppeteer and CDP are incopatible for Issues
130+
const issue = IssuesManager.createIssuesFromProtocolIssue(null, data.issue)[0]; // returns issue wrapped in array, need to get first element
131+
if (!issue) {
132+
return;
133+
}
134+
page.emit('issue', issue);
135+
});
136+
await session.send('Audits.enable');
137+
}
138+
119139
protected splitAfterNavigation(page: Page) {
120140
const navigations = this.storage.get(page);
121141
if (!navigations) {

src/issue-descriptions.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as fs from 'node:fs';
8+
import * as path from 'node:path';
9+
10+
const DESCRIPTIONS_PATH = path.join(
11+
process.cwd(),
12+
'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions',
13+
);
14+
15+
let issueDescriptions: Record<string, string>= {};
16+
17+
/**
18+
* Reads all issue descriptions from the filesystem into memory.
19+
*/
20+
export async function loadIssueDescriptions(): Promise<void> {
21+
if (Object.keys(issueDescriptions).length > 0) {
22+
return;
23+
}
24+
25+
const files = await fs.promises.readdir(DESCRIPTIONS_PATH);
26+
const descriptions: Record<string, string> = {};
27+
28+
for (const file of files) {
29+
if (!file.endsWith('.md')) {
30+
continue;
31+
}
32+
const content = await fs.promises.readFile(
33+
path.join(DESCRIPTIONS_PATH, file),
34+
'utf-8',
35+
);
36+
descriptions[file] = content;
37+
}
38+
39+
issueDescriptions = descriptions;
40+
}
41+
42+
/**
43+
* Gets an issue description from the in-memory cache.
44+
* @param fileName The file name of the issue description.
45+
* @returns The description of the issue, or null if it doesn't exist.
46+
*/
47+
export function getIssueDescription(fileName: string): string | null {
48+
return issueDescriptions[fileName] ?? null;
49+
}

src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import './polyfill.js';
99
import type {Channel} from './browser.js';
1010
import {ensureBrowserConnected, ensureBrowserLaunched} from './browser.js';
1111
import {parseArguments} from './cli.js';
12+
import {loadIssueDescriptions} from './issue-descriptions.js';
1213
import {logger, saveLogsToFile} from './logger.js';
1314
import {McpContext} from './McpContext.js';
1415
import {McpResponse} from './McpResponse.js';
@@ -189,6 +190,7 @@ for (const tool of tools) {
189190
registerTool(tool);
190191
}
191192

193+
await loadIssueDescriptions();
192194
const transport = new StdioServerTransport();
193195
await server.connect(transport);
194196
logger('Chrome DevTools MCP Server connected');

src/tools/console.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import type {ConsoleMessageType} from '../third_party/index.js';
99

1010
import {ToolCategory} from './categories.js';
1111
import {defineTool} from './ToolDefinition.js';
12+
type ConsoleResponseType = ConsoleMessageType | 'issue';
1213

1314
const FILTERABLE_MESSAGE_TYPES: readonly [
14-
ConsoleMessageType,
15-
...ConsoleMessageType[],
15+
ConsoleResponseType,
16+
...ConsoleResponseType[],
1617
] = [
1718
'log',
1819
'debug',
@@ -33,6 +34,7 @@ const FILTERABLE_MESSAGE_TYPES: readonly [
3334
'count',
3435
'timeEnd',
3536
'verbose',
37+
'issue'
3638
];
3739

3840
export const listConsoleMessages = defineTool({

test.txt

Whitespace-only changes.

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
"node_modules/chrome-devtools-frontend/front_end/third_party/marked",
6262
"node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec",
6363
"node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web",
64-
"node_modules/chrome-devtools-frontend/mcp/mcp.ts"
64+
"node_modules/chrome-devtools-frontend/mcp/mcp.ts",
65+
"node_modules/chrome-devtools-frontend/front_end/models/issues_manager",
66+
"node_modules/chrome-devtools-frontend/front_end/third_party/marked"
6567
],
6668
"exclude": ["node_modules/chrome-devtools-frontend/**/*.test.ts"]
6769
}

0 commit comments

Comments
 (0)