Skip to content

Commit f49aa5b

Browse files
author
Natallia Harshunova
committed
Update get_console_message with Issues details
1 parent 2eaf268 commit f49aa5b

File tree

7 files changed

+177
-23
lines changed

7 files changed

+177
-23
lines changed

src/McpContext.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,13 @@ export class McpContext implements Context {
194194
this.logger('no cdpBackendNodeId');
195195
return;
196196
}
197+
if (this.#textSnapshot === null)
198+
throw new Error(
199+
"The snapshot is not defined, can't resolve backendNodeId: " +
200+
cdpBackendNodeId,
201+
);
197202
// TODO: index by backendNodeId instead.
198-
const queue = [this.#textSnapshot?.root];
203+
const queue = [this.#textSnapshot.root];
199204
while (queue.length) {
200205
const current = queue.pop()!;
201206
if (current.backendNodeId === cdpBackendNodeId) {

src/McpResponse.ts

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import {
77
AggregatedIssue,
88
Marked,
9-
findTitleFromMarkdownAst,
9+
MarkdownIssueDescription,
1010
} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
1111

1212
import type {ConsoleMessageData} from './formatters/consoleFormatter.js';
@@ -255,6 +255,12 @@ export class McpResponse implements Response {
255255
}),
256256
),
257257
};
258+
} else if (message instanceof AggregatedIssue) {
259+
const result = mapIssuesMessage(message);
260+
consoleData = {
261+
consoleMessageStableId,
262+
...result,
263+
};
258264
} else {
259265
consoleData = {
260266
consoleMessageStableId,
@@ -308,28 +314,10 @@ export class McpResponse implements Response {
308314
};
309315
}
310316
if (item instanceof AggregatedIssue) {
311-
const count = item.getAggregatedIssuesCount();
312-
const filename = item.getDescription()?.file;
313-
const rawMarkdown = filename
314-
? getIssueDescription(filename)
315-
: null;
316-
if (!rawMarkdown) {
317-
logger(`no markdown ${filename} found for issue:` + item.code);
318-
return null;
319-
}
320-
const markdownAst = Marked.Marked.lexer(rawMarkdown);
321-
const title = findTitleFromMarkdownAst(markdownAst);
322-
if (!title) {
323-
logger('cannot read issue title from ' + filename);
324-
return null;
325-
}
317+
const message = mapIssuesMessage(item);
326318
return {
327319
consoleMessageStableId,
328-
type: 'issue',
329-
item,
330-
message: title,
331-
count,
332-
args: [],
320+
...message,
333321
};
334322
}
335323
return {
@@ -584,3 +572,37 @@ Call ${handleDialog.name} to handle it before continuing.`);
584572
this.#textResponseLines = [];
585573
}
586574
}
575+
576+
function mapIssuesMessage(
577+
message: AggregatedIssue,
578+
): Omit<ConsoleMessageData, 'consoleMessageStableId'> | null {
579+
const count = message.getAggregatedIssuesCount();
580+
const markdownDescription = message.getDescription();
581+
const filename = markdownDescription?.file;
582+
if (!markdownDescription) {
583+
logger(`no description found for issue:` + message.code);
584+
return null;
585+
}
586+
const rawMarkdown = filename ? getIssueDescription(filename) : null;
587+
if (!rawMarkdown) {
588+
logger(`no markdown ${filename} found for issue:` + message.code);
589+
return null;
590+
}
591+
const processedMarkdown = MarkdownIssueDescription.substitutePlaceholders(
592+
rawMarkdown,
593+
markdownDescription.substitutions,
594+
);
595+
const markdownAst = Marked.Marked.lexer(processedMarkdown);
596+
const title = MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
597+
if (!title) {
598+
logger('cannot read issue title from ' + filename);
599+
return null;
600+
}
601+
return {
602+
type: 'issue',
603+
item: message,
604+
message: title,
605+
count,
606+
args: [],
607+
};
608+
}

src/formatters/consoleFormatter.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import {
8+
AggregatedIssue,
9+
MarkdownIssueDescription,
10+
} from '../../node_modules/chrome-devtools-frontend/mcp/mcp.js';
11+
import {ISSUE_UTILS} from '../issue-descriptions.js';
12+
713
export interface ConsoleMessageData {
814
consoleMessageStableId: number;
915
type?: string;
@@ -34,12 +40,19 @@ function getArgs(msg: ConsoleMessageData) {
3440

3541
// The verbose format for a console message, including all details.
3642
export function formatConsoleEventVerbose(msg: ConsoleMessageData): string {
43+
if (msg.item instanceof AggregatedIssue) {
44+
const result = [
45+
`ID: ${msg.consoleMessageStableId}`,
46+
`Message: ${msg.type}> ${formatIssue(msg.item)}`,
47+
];
48+
return result.join('\n');
49+
}
50+
3751
const result = [
3852
`ID: ${msg.consoleMessageStableId}`,
3953
`Message: ${msg.type}> ${msg.message}`,
4054
formatArgs(msg),
4155
].filter(line => !!line);
42-
4356
return result.join('\n');
4457
}
4558

@@ -62,3 +75,36 @@ function formatArgs(consoleData: ConsoleMessageData): string {
6275

6376
return result.join('\n');
6477
}
78+
79+
export function formatIssue(issue: AggregatedIssue): string {
80+
const markdownDescription = issue.getDescription();
81+
const filename = markdownDescription?.file;
82+
const rawMarkdown = filename
83+
? ISSUE_UTILS.getIssueDescription(filename)
84+
: null;
85+
if (!markdownDescription || !rawMarkdown) {
86+
throw new Error('Error parsing issue description ' + issue.code());
87+
}
88+
let processedMarkdown = MarkdownIssueDescription.substitutePlaceholders(
89+
rawMarkdown,
90+
markdownDescription.substitutions,
91+
);
92+
93+
processedMarkdown = processedMarkdown.trim();
94+
// Remove heading in order not to conflict with the result response markdown
95+
if (processedMarkdown.startsWith('# ')) {
96+
processedMarkdown = processedMarkdown.substring(2).trimStart();
97+
}
98+
99+
const result: string[] = [processedMarkdown];
100+
const links = markdownDescription.links;
101+
102+
if (links.length > 0) {
103+
result.push('Learn more:');
104+
for (const link of links) {
105+
result.push(`[${link.linkTitle}](${link.link})`);
106+
}
107+
}
108+
109+
return result.join('\n');
110+
}

src/issue-descriptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,8 @@ export async function loadIssueDescriptions(): Promise<void> {
4747
export function getIssueDescription(fileName: string): string | null {
4848
return issueDescriptions[fileName] ?? null;
4949
}
50+
51+
export const ISSUE_UTILS = {
52+
loadIssueDescriptions,
53+
getIssueDescription,
54+
};

tests/formatters/consoleFormatter.test.js.snapshot

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,12 @@ Message: log> Processing file:
3434
### Arguments
3535
Arg #0: file.txt
3636
`;
37+
38+
exports[`consoleFormatter > formats a console.log message with issue type 1`] = `
39+
Mock Issue Title
40+
41+
This is a mock issue description with a http://example.com/issue-detail.
42+
Learn more:
43+
[Learn more](http://example.com/learnmore)
44+
[Learn more 2](http://example.com/another-learnmore)
45+
`;

tests/formatters/consoleFormatter.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66

77
import {describe, it} from 'node:test';
88

9+
import sinon from 'sinon';
10+
11+
import type {AggregatedIssue} from '../../node_modules/chrome-devtools-frontend/mcp/mcp.js';
912
import type {ConsoleMessageData} from '../../src/formatters/consoleFormatter.js';
1013
import {
1114
formatConsoleEventShort,
1215
formatConsoleEventVerbose,
16+
formatIssue,
1317
} from '../../src/formatters/consoleFormatter.js';
18+
import {ISSUE_UTILS} from '../../src/issue-descriptions.js';
1419

1520
describe('consoleFormatter', () => {
1621
describe('formatConsoleEventShort', () => {
@@ -92,4 +97,34 @@ describe('consoleFormatter', () => {
9297
t.assert.snapshot?.(result);
9398
});
9499
});
100+
101+
it('formats a console.log message with issue type', t => {
102+
class MockAggregatedIssue {
103+
getDescription() {
104+
return {
105+
file: 'mock-issue.md',
106+
substitutions: new Map([
107+
['PLACEHOLDER_URL', 'http://example.com/issue-detail'],
108+
]),
109+
links: [
110+
{link: 'http://example.com/learnmore', linkTitle: 'Learn more'},
111+
{link: 'http://example.com/another-learnmore', linkTitle: 'Learn more 2'},
112+
],
113+
};
114+
}
115+
}
116+
const mockAggregatedIssue = new MockAggregatedIssue();
117+
const getIssueDescriptionStub = sinon.stub(
118+
ISSUE_UTILS,
119+
'getIssueDescription',
120+
);
121+
122+
getIssueDescriptionStub
123+
.withArgs('mock-issue.md')
124+
.returns(
125+
'# Mock Issue Title\n\nThis is a mock issue description with a {PLACEHOLDER_URL}.',
126+
);
127+
const result = formatIssue(mockAggregatedIssue as AggregatedIssue);
128+
t.assert.snapshot?.(result);
129+
});
95130
});

tests/tools/console.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ describe('console', () => {
128128
});
129129

130130
describe('get_console_message', () => {
131+
beforeEach(() => {
132+
setIssuesEnabled(true);
133+
});
134+
afterEach(() => {
135+
setIssuesEnabled(false);
136+
});
131137
it('gets a specific console message', async () => {
132138
await withBrowser(async (response, context) => {
133139
const page = await context.newPage();
@@ -149,5 +155,31 @@ describe('console', () => {
149155
);
150156
});
151157
});
158+
159+
it('lists issues', async () => {
160+
await withBrowser(async (response, context) => {
161+
const page = await context.newPage();
162+
const issuePromise = new Promise<void>(resolve => {
163+
page.once('issue', () => {
164+
resolve();
165+
});
166+
});
167+
await page.setContent('<input type="text" name="username" />');
168+
await issuePromise;
169+
await listConsoleMessages.handler({params: {}}, response, context);
170+
await getConsoleMessage.handler(
171+
{params: {msgid: 1}},
172+
response,
173+
context,
174+
);
175+
const formattedResponse = await response.handle('test', context);
176+
const textContent = formattedResponse[0] as {text: string};
177+
assert.ok(
178+
textContent.text.includes(
179+
"Message: issue> An element doesn't have an autocomplete attribute",
180+
),
181+
);
182+
});
183+
});
152184
});
153185
});

0 commit comments

Comments
 (0)