Skip to content

Commit 5db74ed

Browse files
Natallia HarshunovaOrKoN
authored andcommitted
add elementsID into console data message
1 parent 2d89865 commit 5db74ed

File tree

8 files changed

+106
-33
lines changed

8 files changed

+106
-33
lines changed

src/McpResponse.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export class McpResponse implements Response {
255255
const mappedIssueMessage = mapIssueToMessageObject(message);
256256
if (!mappedIssueMessage)
257257
throw new Error(
258-
"Can't prpovide detals for the msgid " + consoleMessageStableId,
258+
"Can't provide detals for the msgid " + consoleMessageStableId,
259259
);
260260
consoleData = {
261261
consoleMessageStableId,
@@ -402,7 +402,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
402402
}
403403

404404
response.push(...this.#formatNetworkRequestData(context, data.bodies));
405-
response.push(...this.#formatConsoleData(data.consoleData));
405+
response.push(...this.#formatConsoleData(context, data.consoleData));
406406

407407
if (this.#networkRequestsOptions?.include) {
408408
let requests = context.getNetworkRequests(
@@ -500,13 +500,16 @@ Call ${handleDialog.name} to handle it before continuing.`);
500500
};
501501
}
502502

503-
#formatConsoleData(data: ConsoleMessageData | undefined): string[] {
503+
#formatConsoleData(
504+
context: McpContext,
505+
data: ConsoleMessageData | undefined,
506+
): string[] {
504507
const response: string[] = [];
505508
if (!data) {
506509
return response;
507510
}
508511

509-
response.push(formatConsoleEventVerbose(data));
512+
response.push(formatConsoleEventVerbose(data, context));
510513
return response;
511514
}
512515

src/formatters/consoleFormatter.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import type {AggregatedIssue} from '../../node_modules/chrome-devtools-frontend/mcp/mcp.js';
8+
import type {McpContext} from '../McpContext.js';
89

910
export interface ConsoleMessageData {
1011
consoleMessageStableId: number;
@@ -36,11 +37,14 @@ function getArgs(msg: ConsoleMessageData) {
3637
}
3738

3839
// The verbose format for a console message, including all details.
39-
export function formatConsoleEventVerbose(msg: ConsoleMessageData): string {
40+
export function formatConsoleEventVerbose(
41+
msg: ConsoleMessageData,
42+
context?: McpContext,
43+
): string {
4044
const aggregatedIssue = msg.item;
4145
const result = [
4246
`ID: ${msg.consoleMessageStableId}`,
43-
`Message: ${msg.type}> ${aggregatedIssue ? formatIssue(aggregatedIssue, msg.description) : msg.message}`,
47+
`Message: ${msg.type}> ${aggregatedIssue ? formatIssue(aggregatedIssue, msg.description, context) : msg.message}`,
4448
aggregatedIssue ? undefined : formatArgs(msg),
4549
].filter(line => !!line);
4650
return result.join('\n');
@@ -65,10 +69,15 @@ function formatArgs(consoleData: ConsoleMessageData): string {
6569

6670
return result.join('\n');
6771
}
68-
72+
interface IssueDetailsWithResources {
73+
violatingNodeId?: number;
74+
nodeId?: number;
75+
documentNodeId?: number;
76+
}
6977
export function formatIssue(
7078
issue: AggregatedIssue,
7179
description?: string,
80+
context?: McpContext,
7281
): string {
7382
const result: string[] = [];
7483

@@ -87,6 +96,45 @@ export function formatIssue(
8796
}
8897
}
8998

99+
const issues: Array<{details?: () => IssueDetailsWithResources}> = [
100+
...issue.getGenericIssues(),
101+
...issue.getLowContrastIssues(),
102+
...issue.getElementAccessibilityIssues(),
103+
...issue.getQuirksModeIssues(),
104+
// ...issue.getAttributionReportingIssues(), // Uncomment when AttributionReportingIssue has details()
105+
];
106+
const affectedElement: Array<{uid?: string; data?: object}> = [];
107+
for (const singleIssue of issues) {
108+
if (!singleIssue.details) break;
109+
110+
const details =
111+
singleIssue.details() as unknown as IssueDetailsWithResources;
112+
let uid;
113+
if (details.violatingNodeId && context) {
114+
uid = context.resolveCdpElementId(details.violatingNodeId);
115+
}
116+
if (details.nodeId && context) {
117+
uid = context.resolveCdpElementId(details.nodeId);
118+
}
119+
if (details.documentNodeId && context) {
120+
uid = context.resolveCdpElementId(details.documentNodeId);
121+
}
122+
123+
// eslint-disable-next-line
124+
const data = structuredClone(details) as any;
125+
delete data.violatingNodeId;
126+
delete data.errorType;
127+
delete data.frameId;
128+
affectedElement.push({uid, data: data});
129+
}
130+
if (affectedElement.length) {
131+
result.push('### Affected resources');
132+
}
133+
result.push(
134+
...affectedElement.map(item => {
135+
return `uid=${item.uid} data=${JSON.stringify(item.data)}`;
136+
}),
137+
);
90138
if (result.length === 0)
91139
return 'No details provided for the issue ' + issue.code();
92140
return result.join('\n');

tests/McpResponse.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import {tmpdir} from 'node:os';
1010
import {join} from 'node:path';
1111
import {describe, it} from 'node:test';
1212

13-
import sinon from 'sinon';
14-
15-
import {AggregatedIssue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
16-
1713
import {
14+
getMockAggregatedIssue,
1815
getMockRequest,
1916
getMockResponse,
2017
html,
@@ -302,7 +299,7 @@ describe('McpResponse', () => {
302299

303300
it("doesn't list the issue message if mapping returns null", async () => {
304301
await withBrowser(async (response, context) => {
305-
const mockAggregatedIssue = sinon.createStubInstance(AggregatedIssue);
302+
const mockAggregatedIssue = getMockAggregatedIssue();
306303
const mockDescription = {
307304
file: 'not-existing-description-file.md',
308305
links: [],
@@ -321,7 +318,7 @@ describe('McpResponse', () => {
321318

322319
it('throws error if mapping returns null on get issue details', async () => {
323320
await withBrowser(async (response, context) => {
324-
const mockAggregatedIssue = sinon.createStubInstance(AggregatedIssue);
321+
const mockAggregatedIssue = getMockAggregatedIssue();
325322
const mockDescription = {
326323
file: 'not-existing-description-file.md',
327324
links: [],
@@ -335,7 +332,7 @@ describe('McpResponse', () => {
335332
try {
336333
await response.handle('test', context);
337334
} catch (e) {
338-
assert.ok(e.message.includes("Can't prpovide detals for the msgid 1"));
335+
assert.ok(e.message.includes("Can't provide detals for the msgid 1"));
339336
}
340337
});
341338
});

tests/formatters/consoleFormatter.test.js.snapshot

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@ This is a mock issue description
4343
Learn more:
4444
[Learn more](http://example.com/learnmore)
4545
[Learn more 2](http://example.com/another-learnmore)
46+
### Affected resources
47+
uid=undefined data={"violatingNodeAttribute":"test"}
4648
`;

tests/formatters/consoleFormatter.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

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

9-
import sinon from 'sinon';
10-
11-
import {AggregatedIssue} from '../../node_modules/chrome-devtools-frontend/mcp/mcp.js';
129
import type {ConsoleMessageData} from '../../src/formatters/consoleFormatter.js';
1310
import {
1411
formatConsoleEventShort,
1512
formatConsoleEventVerbose,
1613
} from '../../src/formatters/consoleFormatter.js';
14+
import {getMockAggregatedIssue} from '../utils.js';
1715

1816
describe('consoleFormatter', () => {
1917
describe('formatConsoleEventShort', () => {
@@ -97,7 +95,15 @@ describe('consoleFormatter', () => {
9795
});
9896

9997
it('formats a console.log message with issue type', t => {
100-
const mockAggregatedIssue = sinon.createStubInstance(AggregatedIssue);
98+
const testGenericIssue = {
99+
details: () => {
100+
return {
101+
violatingNodeId: 2,
102+
violatingNodeAttribute: 'test',
103+
};
104+
},
105+
};
106+
const mockAggregatedIssue = getMockAggregatedIssue();
101107
const mockDescription = {
102108
file: 'mock.md',
103109
links: [
@@ -109,6 +115,8 @@ describe('consoleFormatter', () => {
109115
],
110116
};
111117
mockAggregatedIssue.getDescription.returns(mockDescription);
118+
// @ts-expect-error generic issue stub bypass
119+
mockAggregatedIssue.getGenericIssues.returns(new Set([testGenericIssue]));
112120
const mockDescriptionFileContent =
113121
'# Mock Issue Title\n\nThis is a mock issue description';
114122

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
exports[`console > get_console_message > issues type > gets issue details 1`] = `
2+
# test response
3+
ID: 1
4+
Message: issue> An element doesn't have an autocomplete attribute
5+
6+
A form field has an \`id\` or \`name\` attribute that the browser's autofill recognizes. However, it doesn't have an \`autocomplete\` attribute assigned. This might prevent the browser from correctly autofilling the form.
7+
8+
To fix this issue, provide an \`autocomplete\` attribute.
9+
Learn more:
10+
[HTML attribute: autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values)
11+
### Affected resources
12+
uid=1_1 data={"violatingNodeAttribute":"name"}
13+
`;

tests/tools/console.test.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@ import {
1717
import {withBrowser} from '../utils.js';
1818

1919
describe('console', () => {
20+
before(async () => {
21+
await loadIssueDescriptions();
22+
});
2023
describe('list_console_messages', () => {
21-
before(async () => {
22-
await loadIssueDescriptions();
23-
});
24-
2524
it('list messages', async () => {
2625
await withBrowser(async (response, context) => {
2726
await listConsoleMessages.handler({params: {}}, response, context);
@@ -160,7 +159,7 @@ describe('console', () => {
160159
setIssuesEnabled(false);
161160
});
162161

163-
it('gets issue details', async () => {
162+
it('gets issue details', async t => {
164163
await withBrowser(async (response, context) => {
165164
const page = await context.newPage();
166165
const issuePromise = new Promise<void>(resolve => {
@@ -169,6 +168,7 @@ describe('console', () => {
169168
});
170169
});
171170
await page.setContent('<input type="text" name="username" />');
171+
await context.createTextSnapshot();
172172
await issuePromise;
173173
await listConsoleMessages.handler({params: {}}, response, context);
174174
const response2 = new McpResponse();
@@ -178,16 +178,7 @@ describe('console', () => {
178178
context,
179179
);
180180
const formattedResponse = await response2.handle('test', context);
181-
const textContent = formattedResponse[0] as {text: string};
182-
const learnMoreLinks =
183-
'[HTML attribute: autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values)';
184-
const detailsDescription =
185-
"A form field has an `id` or `name` attribute that the browser's autofill recognizes. However, it doesn't have an `autocomplete` attribute assigned. This might prevent the browser from correctly autofilling the form.\n\nTo fix this issue, provide an `autocomplete` attribute.";
186-
const title =
187-
"Message: issue> An element doesn't have an autocomplete attribute";
188-
assert.ok(textContent.text.includes(title));
189-
assert.ok(textContent.text.includes(detailsDescription));
190-
assert.ok(textContent.text.includes(learnMoreLinks));
181+
t.assert.snapshot?.(formattedResponse[0].text);
191182
});
192183
});
193184
});

tests/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import type {
1313
HTTPResponse,
1414
LaunchOptions,
1515
} from 'puppeteer-core';
16+
import sinon from 'sinon';
1617

18+
import {AggregatedIssue} from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
1719
import {McpContext} from '../src/McpContext.js';
1820
import {McpResponse} from '../src/McpResponse.js';
1921
import {stableIdSymbol} from '../src/PageCollector.js';
@@ -180,3 +182,12 @@ export function stabilizeResponseOutput(text: unknown) {
180182
output = output.replaceAll(savedSnapshot, 'Saved snapshot to <file>');
181183
return output;
182184
}
185+
186+
export function getMockAggregatedIssue(): sinon.SinonStubbedInstance<AggregatedIssue> {
187+
const mockAggregatedIssue = sinon.createStubInstance(AggregatedIssue);
188+
mockAggregatedIssue.getGenericIssues.returns(new Set());
189+
mockAggregatedIssue.getLowContrastIssues.returns(new Set());
190+
mockAggregatedIssue.getElementAccessibilityIssues.returns(new Set());
191+
mockAggregatedIssue.getQuirksModeIssues.returns(new Set());
192+
return mockAggregatedIssue;
193+
}

0 commit comments

Comments
 (0)