Skip to content

Commit 63b826c

Browse files
nattalliusNatallia Harshunova
andauthored
chore: report issues in details in get_console_mesasge (#611)
Co-authored-by: Natallia Harshunova <nharshunova@chromium.org>
1 parent 2d89865 commit 63b826c

File tree

8 files changed

+229
-33
lines changed

8 files changed

+229
-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: 96 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,19 @@ 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+
request?: {
77+
requestId?: string;
78+
url: string;
79+
};
80+
}
6981
export function formatIssue(
7082
issue: AggregatedIssue,
7183
description?: string,
84+
context?: McpContext,
7285
): string {
7386
const result: string[] = [];
7487

@@ -87,6 +100,86 @@ export function formatIssue(
87100
}
88101
}
89102

103+
const issues: Array<{
104+
details?: () => IssueDetailsWithResources;
105+
getDetails?: () => IssueDetailsWithResources;
106+
}> = [
107+
...issue.getCorsIssues(),
108+
...issue.getMixedContentIssues(),
109+
...issue.getGenericIssues(),
110+
...issue.getLowContrastIssues(),
111+
...issue.getElementAccessibilityIssues(),
112+
...issue.getQuirksModeIssues(),
113+
];
114+
const affectedResources: Array<{
115+
uid?: string;
116+
data?: object;
117+
request?: string | number;
118+
}> = [];
119+
for (const singleIssue of issues) {
120+
if (!singleIssue.details && !singleIssue.getDetails) continue;
121+
122+
let details =
123+
singleIssue.details?.() as unknown as IssueDetailsWithResources;
124+
if (!details)
125+
details =
126+
singleIssue.getDetails?.() as unknown as IssueDetailsWithResources;
127+
if (!details) continue;
128+
129+
let uid;
130+
let request: number | string | undefined;
131+
if (details.violatingNodeId && context) {
132+
uid = context.resolveCdpElementId(details.violatingNodeId);
133+
}
134+
if (details.nodeId && context) {
135+
uid = context.resolveCdpElementId(details.nodeId);
136+
}
137+
if (details.documentNodeId && context) {
138+
uid = context.resolveCdpElementId(details.documentNodeId);
139+
}
140+
141+
if (details.request) {
142+
request = details.request.url;
143+
if (details.request.requestId && context) {
144+
const resolvedId = context.resolveCdpRequestId(
145+
details.request.requestId,
146+
);
147+
if (resolvedId) {
148+
request = resolvedId;
149+
}
150+
}
151+
}
152+
153+
// eslint-disable-next-line
154+
const data = structuredClone(details) as any;
155+
delete data.violatingNodeId;
156+
delete data.nodeId;
157+
delete data.documentNodeId;
158+
delete data.errorType;
159+
delete data.frameId;
160+
delete data.request;
161+
affectedResources.push({
162+
uid,
163+
data: data,
164+
request,
165+
});
166+
}
167+
if (affectedResources.length) {
168+
result.push('### Affected resources');
169+
}
170+
result.push(
171+
...affectedResources.map(item => {
172+
const details = [];
173+
if (item.uid) details.push(`uid=${item.uid}`);
174+
if (item.request) {
175+
details.push(
176+
(typeof item.request === 'number' ? `reqid=` : 'url=') + item.request,
177+
);
178+
}
179+
if (item.data) details.push(`data=${JSON.stringify(item.data)}`);
180+
return details.join(' ');
181+
}),
182+
);
90183
if (result.length === 0)
91184
return 'No details provided for the issue ' + issue.code();
92185
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+
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
exports[`console > get_console_message > issues type > gets issue details with node id parsing 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+
`;
14+
15+
exports[`console > get_console_message > issues type > gets issue details with request id parsing 1`] = `
16+
# test response
17+
ID: <ID>
18+
Message: issue> Ensure CORS response header values are valid
19+
20+
A cross-origin resource sharing (CORS) request was blocked because of invalid or missing response headers of the request or the associated [preflight request](issueCorsPreflightRequest).
21+
22+
To fix this issue, ensure the response to the CORS request and/or the associated [preflight request](issueCorsPreflightRequest) are not missing headers and use valid header values.
23+
24+
Note that if an opaque response is sufficient, the request's mode can be set to \`no-cors\` to fetch the resource with CORS disabled; that way CORS headers are not required but the response content is inaccessible (opaque).
25+
Learn more:
26+
[Cross-Origin Resource Sharing (\`CORS\`)](https://web.dev/cross-origin-resource-sharing)
27+
### Affected resources
28+
reqid=1 data={"corsErrorStatus":{"corsError":"PreflightMissingAllowOriginHeader","failedParameter":""},"isWarning":false,"initiatorOrigin":"","clientSecurityState":{"initiatorIsSecureContext":false,"initiatorIPAddressSpace":"Loopback","privateNetworkRequestPolicy":"BlockFromInsecureToMorePrivate"}}
29+
`;

0 commit comments

Comments
 (0)