lighthouse 13.1.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/bin.js +5 -0
- package/core/audits/accessibility/autocomplete-valid.d.ts +10 -0
- package/core/audits/accessibility/autocomplete-valid.js +44 -0
- package/core/audits/accessibility/presentation-role-conflict.d.ts +10 -0
- package/core/audits/accessibility/presentation-role-conflict.js +46 -0
- package/core/audits/accessibility/svg-img-alt.d.ts +10 -0
- package/core/audits/accessibility/svg-img-alt.js +44 -0
- package/core/audits/agentic/agent-accessibility-tree.d.ts +19 -0
- package/core/audits/agentic/agent-accessibility-tree.js +115 -0
- package/core/audits/agentic/llms-txt.d.ts +20 -0
- package/core/audits/agentic/llms-txt.js +111 -0
- package/core/audits/insights/insight-audit.d.ts +2 -2
- package/core/audits/insights/insight-audit.js +16 -6
- package/core/audits/layout-shifts.js +1 -1
- package/core/audits/server-response-time.js +3 -3
- package/core/audits/webmcp-form-coverage.d.ts +16 -0
- package/core/audits/webmcp-form-coverage.js +90 -0
- package/core/audits/webmcp-registered-tools.d.ts +21 -0
- package/core/audits/webmcp-registered-tools.js +149 -0
- package/core/audits/webmcp-schema-validity.d.ts +22 -0
- package/core/audits/webmcp-schema-validity.js +141 -0
- package/core/computed/document-urls.js +0 -1
- package/core/computed/main-resource.js +0 -2
- package/core/computed/metrics/lantern-metric.js +4 -4
- package/core/computed/metrics/lcp-breakdown.js +1 -1
- package/core/computed/metrics/time-to-first-byte.js +1 -1
- package/core/computed/navigation-insights.js +2 -1
- package/core/computed/network-analysis.js +0 -1
- package/core/config/agentic-browsing-config.d.ts +12 -0
- package/core/config/agentic-browsing-config.js +73 -0
- package/core/config/default-config.js +6 -0
- package/core/gather/gatherers/accessibility.js +5 -1
- package/core/gather/gatherers/agentic/llms-txt.d.ts +10 -0
- package/core/gather/gatherers/agentic/llms-txt.js +28 -0
- package/core/gather/gatherers/inputs.js +2 -0
- package/core/gather/gatherers/meta-elements.js +1 -1
- package/core/gather/gatherers/trace-elements.js +1 -1
- package/core/gather/gatherers/webmcp-schema.d.ts +25 -0
- package/core/gather/gatherers/webmcp-schema.js +105 -0
- package/core/gather/gatherers/webmcp.d.ts +58 -0
- package/core/gather/gatherers/webmcp.js +159 -0
- package/core/index.d.ts +1 -0
- package/core/index.js +1 -0
- package/core/lib/cdt/generated/SourceMap.js +2 -2
- package/core/lib/navigation-error.js +0 -6
- package/core/lib/network-request.js +0 -1
- package/core/lib/page-functions.d.ts +3 -3
- package/core/lib/page-functions.js +11 -4
- package/core/lib/tracehouse/trace-processor.d.ts +5 -4
- package/core/lib/tracehouse/trace-processor.js +85 -19
- package/core/runner.js +3 -0
- package/core/scoring.d.ts +1 -0
- package/dist/report/bundle.esm.js +1 -1
- package/dist/report/flow.js +3 -3
- package/dist/report/standalone.js +1 -1
- package/flow-report/src/summary/category.tsx +1 -1
- package/package.json +11 -11
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/report-utils.d.ts +2 -1
- package/report/renderer/report-utils.js +7 -2
- package/shared/localization/locales/ar-XB.json +72 -36
- package/shared/localization/locales/ar.json +72 -36
- package/shared/localization/locales/bg.json +72 -36
- package/shared/localization/locales/ca.json +72 -36
- package/shared/localization/locales/cs.json +72 -36
- package/shared/localization/locales/da.json +74 -38
- package/shared/localization/locales/de.json +72 -36
- package/shared/localization/locales/el.json +73 -37
- package/shared/localization/locales/en-GB.json +74 -38
- package/shared/localization/locales/en-US.json +245 -17
- package/shared/localization/locales/en-XL.json +245 -17
- package/shared/localization/locales/es-419.json +72 -36
- package/shared/localization/locales/es.json +73 -37
- package/shared/localization/locales/fi.json +72 -36
- package/shared/localization/locales/fil.json +74 -38
- package/shared/localization/locales/fr.json +162 -126
- package/shared/localization/locales/he.json +74 -38
- package/shared/localization/locales/hi.json +73 -37
- package/shared/localization/locales/hr.json +72 -36
- package/shared/localization/locales/hu.json +73 -37
- package/shared/localization/locales/id.json +74 -38
- package/shared/localization/locales/it.json +72 -36
- package/shared/localization/locales/ja.json +72 -36
- package/shared/localization/locales/ko.json +72 -36
- package/shared/localization/locales/lt.json +72 -36
- package/shared/localization/locales/lv.json +72 -36
- package/shared/localization/locales/nl.json +73 -37
- package/shared/localization/locales/no.json +72 -36
- package/shared/localization/locales/pl.json +72 -36
- package/shared/localization/locales/pt-PT.json +72 -36
- package/shared/localization/locales/pt.json +74 -38
- package/shared/localization/locales/ro.json +72 -36
- package/shared/localization/locales/ru.json +72 -36
- package/shared/localization/locales/sk.json +72 -36
- package/shared/localization/locales/sl.json +72 -36
- package/shared/localization/locales/sr-Latn.json +73 -37
- package/shared/localization/locales/sr.json +73 -37
- package/shared/localization/locales/sv.json +75 -39
- package/shared/localization/locales/ta.json +73 -37
- package/shared/localization/locales/te.json +72 -36
- package/shared/localization/locales/th.json +73 -37
- package/shared/localization/locales/tr.json +72 -36
- package/shared/localization/locales/uk.json +72 -36
- package/shared/localization/locales/vi.json +74 -38
- package/shared/localization/locales/zh-HK.json +72 -36
- package/shared/localization/locales/zh-TW.json +74 -38
- package/shared/localization/locales/zh.json +75 -39
- package/types/artifacts.d.ts +33 -0
- package/types/config.d.ts +1 -0
- package/types/lhr/lhr.d.ts +11 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default WebMCPRegisteredTools;
|
|
2
|
+
declare class WebMCPRegisteredTools extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @return {LH.Audit.Product}
|
|
6
|
+
*/
|
|
7
|
+
static audit(artifacts: LH.Artifacts): LH.Audit.Product;
|
|
8
|
+
}
|
|
9
|
+
export namespace UIStrings {
|
|
10
|
+
let title: string;
|
|
11
|
+
let description: string;
|
|
12
|
+
let columnTool: string;
|
|
13
|
+
let columnDescription: string;
|
|
14
|
+
let columnImperativeLocation: string;
|
|
15
|
+
let columnDeclarativeElement: string;
|
|
16
|
+
let columnInputSchema: string;
|
|
17
|
+
let titleImperativeTools: string;
|
|
18
|
+
let titleDeclarativeTools: string;
|
|
19
|
+
}
|
|
20
|
+
import { Audit } from './audit.js';
|
|
21
|
+
//# sourceMappingURL=webmcp-registered-tools.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Audit that lists registered WebMCP tools.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {Audit} from './audit.js';
|
|
12
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
13
|
+
|
|
14
|
+
const UIStrings = {
|
|
15
|
+
/** Title of a Lighthouse audit that lists registered WebMCP tools. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
16
|
+
title: 'WebMCP tools registered',
|
|
17
|
+
/** Description of a Lighthouse audit that lists registered WebMCP tools. This is displayed after a user expands the section to see more. No character length limits. "WebMCP" stands for "Web Model Context Protocol", neither should be translated. */
|
|
18
|
+
description: 'Lists the WebMCP tools registered at the time of analysis. ',
|
|
19
|
+
/** Label for a column in a data table; entries will be the name of a WebMCP tool. */
|
|
20
|
+
columnTool: 'Tool name',
|
|
21
|
+
/** Label for a column in a data table; entries will be the description of a WebMCP tool. */
|
|
22
|
+
columnDescription: 'Description',
|
|
23
|
+
/** Label for a column in a data table; entries will be the source location where an imperative WebMCP tool was registered. */
|
|
24
|
+
columnImperativeLocation: 'Source Location',
|
|
25
|
+
/** Label for a column in a data table; entries will be the DOM element associated with a declarative WebMCP tool. */
|
|
26
|
+
columnDeclarativeElement: 'Element',
|
|
27
|
+
/** Label for a column in a data table; entries will be the input schema of a WebMCP tool. */
|
|
28
|
+
columnInputSchema: 'Input schema',
|
|
29
|
+
/** Title for the table listing imperative WebMCP tools. */
|
|
30
|
+
titleImperativeTools: 'Imperative Tools',
|
|
31
|
+
/** Title for the table listing declarative WebMCP tools. */
|
|
32
|
+
titleDeclarativeTools: 'Declarative Tools',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
36
|
+
|
|
37
|
+
class WebMCPRegisteredTools extends Audit {
|
|
38
|
+
/**
|
|
39
|
+
* @return {LH.Audit.Meta}
|
|
40
|
+
*/
|
|
41
|
+
static get meta() {
|
|
42
|
+
return {
|
|
43
|
+
id: 'webmcp-registered-tools',
|
|
44
|
+
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
45
|
+
title: str_(UIStrings.title),
|
|
46
|
+
description: str_(UIStrings.description),
|
|
47
|
+
requiredArtifacts: ['WebMCP'],
|
|
48
|
+
supportedModes: ['navigation', 'snapshot'],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {LH.Artifacts} artifacts
|
|
54
|
+
* @return {LH.Audit.Product}
|
|
55
|
+
*/
|
|
56
|
+
static audit(artifacts) {
|
|
57
|
+
if (!artifacts.WebMCP.isSupported) {
|
|
58
|
+
return {
|
|
59
|
+
notApplicable: true,
|
|
60
|
+
score: 1,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tools = artifacts.WebMCP.tools || [];
|
|
65
|
+
|
|
66
|
+
const imperativeResults = [];
|
|
67
|
+
const declarativeResults = [];
|
|
68
|
+
|
|
69
|
+
for (const tool of tools) {
|
|
70
|
+
const isDeclarative = typeof tool.backendNodeId === 'number';
|
|
71
|
+
|
|
72
|
+
let source;
|
|
73
|
+
let element;
|
|
74
|
+
|
|
75
|
+
if (isDeclarative && tool.nodeDetails) {
|
|
76
|
+
element = Audit.makeNodeItem(tool.nodeDetails);
|
|
77
|
+
} else if (isDeclarative) {
|
|
78
|
+
element = `Node ID: ${tool.backendNodeId}`;
|
|
79
|
+
} else if (tool.stackTrace) {
|
|
80
|
+
const callFrame = tool.stackTrace.callFrames?.[0];
|
|
81
|
+
if (callFrame) {
|
|
82
|
+
source =
|
|
83
|
+
Audit.makeSourceLocation(
|
|
84
|
+
callFrame.url, callFrame.lineNumber, callFrame.columnNumber || 0
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const item = {
|
|
90
|
+
tool: tool.name,
|
|
91
|
+
description: tool.description,
|
|
92
|
+
inputSchema: JSON.stringify(tool.inputSchema, null, 2),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (isDeclarative) {
|
|
96
|
+
declarativeResults.push({
|
|
97
|
+
...item,
|
|
98
|
+
element,
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
imperativeResults.push({
|
|
102
|
+
...item,
|
|
103
|
+
source,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const list = [];
|
|
109
|
+
|
|
110
|
+
if (imperativeResults.length > 0) {
|
|
111
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
112
|
+
const headings = [
|
|
113
|
+
{key: 'tool', valueType: 'text', label: str_(UIStrings.columnTool)},
|
|
114
|
+
{key: 'description', valueType: 'text', label: str_(UIStrings.columnDescription)},
|
|
115
|
+
{key: 'source', valueType: 'source-location',
|
|
116
|
+
label: str_(UIStrings.columnImperativeLocation)},
|
|
117
|
+
{key: 'inputSchema', valueType: 'code', label: str_(UIStrings.columnInputSchema)},
|
|
118
|
+
];
|
|
119
|
+
const table = Audit.makeTableDetails(headings, imperativeResults);
|
|
120
|
+
list.push(Audit.makeListDetailSectionItem(table, str_(UIStrings.titleImperativeTools)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (declarativeResults.length > 0) {
|
|
124
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
125
|
+
const headings = [
|
|
126
|
+
{key: 'tool', valueType: 'text', label: str_(UIStrings.columnTool)},
|
|
127
|
+
{key: 'description', valueType: 'text', label: str_(UIStrings.columnDescription)},
|
|
128
|
+
{key: 'element', valueType: 'node', label: str_(UIStrings.columnDeclarativeElement)},
|
|
129
|
+
{key: 'inputSchema', valueType: 'code', label: str_(UIStrings.columnInputSchema)},
|
|
130
|
+
];
|
|
131
|
+
const table = Audit.makeTableDetails(headings, declarativeResults);
|
|
132
|
+
list.push(Audit.makeListDetailSectionItem(table, str_(UIStrings.titleDeclarativeTools)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (list.length === 0) {
|
|
136
|
+
return {
|
|
137
|
+
score: 1,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
score: 1,
|
|
143
|
+
details: Audit.makeListDetails(list),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default WebMCPRegisteredTools;
|
|
149
|
+
export {UIStrings};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default WebMcpSchemaValidity;
|
|
2
|
+
declare class WebMcpSchemaValidity extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @return {Promise<LH.Audit.Product>}
|
|
6
|
+
*/
|
|
7
|
+
static audit(artifacts: LH.Artifacts): Promise<LH.Audit.Product>;
|
|
8
|
+
}
|
|
9
|
+
export namespace UIStrings {
|
|
10
|
+
let title: string;
|
|
11
|
+
let failureTitle: string;
|
|
12
|
+
let description: string;
|
|
13
|
+
let columnElement: string;
|
|
14
|
+
let columnIssue: string;
|
|
15
|
+
let missingToolName: string;
|
|
16
|
+
let missingToolDescription: string;
|
|
17
|
+
let missingRequiredParamName: string;
|
|
18
|
+
let missingOptionalParamName: string;
|
|
19
|
+
let missingParamDescription: string;
|
|
20
|
+
}
|
|
21
|
+
import { Audit } from './audit.js';
|
|
22
|
+
//# sourceMappingURL=webmcp-schema-validity.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Audit} from './audit.js';
|
|
8
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
9
|
+
|
|
10
|
+
const UIStrings = {
|
|
11
|
+
/** Title of a Lighthouse audit that evaluates WebMCP schema validity. This descriptive title is shown to users when there are no schema validity issues. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
12
|
+
title: 'WebMCP schemas are valid',
|
|
13
|
+
/** Title of a Lighthouse audit that provides detail on WebMCP schema validity. This descriptive title is shown to users when there are schema validity issues. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
14
|
+
failureTitle: 'WebMCP schemas are invalid',
|
|
15
|
+
/** Description of a Lighthouse audit that tells the user why they should ensure WebMCP schemas are valid. This is displayed after a user expands the section to see more. No character length limits. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
16
|
+
description: 'Valid WebMCP schemas are required for AI agents to ' +
|
|
17
|
+
' understand and interact with tools correctly. ' +
|
|
18
|
+
'Please fix any errors or warnings reported by the browser.',
|
|
19
|
+
/** Header of the table column which displays the element. */
|
|
20
|
+
columnElement: 'Element',
|
|
21
|
+
/** Header of the table column which displays the issue. */
|
|
22
|
+
columnIssue: 'Issue',
|
|
23
|
+
/** Descriptive reason for why a form fails WebMCP validation due to missing `toolname` attribute. */
|
|
24
|
+
missingToolName: 'Form level `toolname` attribute is missing. Add it to define the tool name.',
|
|
25
|
+
/** Descriptive reason for why a form fails WebMCP validation due to missing `tooldescription` attribute. */
|
|
26
|
+
missingToolDescription: 'Form level `tooldescription` attribute is missing. ' +
|
|
27
|
+
'Add it to describe the tool for AI agents.',
|
|
28
|
+
/** Descriptive reason for why a form field fails WebMCP validation due to missing `name` attribute for a required field. */
|
|
29
|
+
missingRequiredParamName: 'Missing `name` attribute for a required field. ' +
|
|
30
|
+
'Add it to define the parameter name.',
|
|
31
|
+
/** Descriptive reason for why a form field fails WebMCP validation due to missing `name` attribute for an optional field. */
|
|
32
|
+
missingOptionalParamName: 'Missing `name` attribute for an optional field. ' +
|
|
33
|
+
'Add it to define the parameter name.',
|
|
34
|
+
/** Descriptive reason for why a form field fails WebMCP validation due to missing description. */
|
|
35
|
+
missingParamDescription: 'Add a description to make this form more accessible for AI agents.',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
39
|
+
|
|
40
|
+
class WebMcpSchemaValidity extends Audit {
|
|
41
|
+
/**
|
|
42
|
+
* @return {LH.Audit.Meta}
|
|
43
|
+
*/
|
|
44
|
+
static get meta() {
|
|
45
|
+
return {
|
|
46
|
+
id: 'webmcp-schema-validity',
|
|
47
|
+
title: str_(UIStrings.title),
|
|
48
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
49
|
+
description: str_(UIStrings.description),
|
|
50
|
+
requiredArtifacts: ['WebMCP', 'WebMcpSchemaIssues'],
|
|
51
|
+
supportedModes: ['navigation', 'snapshot'],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {LH.Artifacts} artifacts
|
|
57
|
+
* @return {Promise<LH.Audit.Product>}
|
|
58
|
+
*/
|
|
59
|
+
static async audit(artifacts) {
|
|
60
|
+
if (!artifacts.WebMCP.isSupported) {
|
|
61
|
+
return {
|
|
62
|
+
notApplicable: true,
|
|
63
|
+
score: 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @enum {number} */
|
|
68
|
+
const Severity = {
|
|
69
|
+
ERROR: 1,
|
|
70
|
+
WARNING: 2,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** @type {Record<string, {severity: Severity, description: LH.IcuMessage}>} */
|
|
74
|
+
const issueConfigs = {
|
|
75
|
+
'FormModelContextMissingToolName': {
|
|
76
|
+
severity: Severity.ERROR,
|
|
77
|
+
description: str_(UIStrings.missingToolName),
|
|
78
|
+
},
|
|
79
|
+
'FormModelContextMissingToolDescription': {
|
|
80
|
+
severity: Severity.ERROR,
|
|
81
|
+
description: str_(UIStrings.missingToolDescription),
|
|
82
|
+
},
|
|
83
|
+
'FormModelContextRequiredParameterMissingName': {
|
|
84
|
+
severity: Severity.ERROR,
|
|
85
|
+
description: str_(UIStrings.missingRequiredParamName),
|
|
86
|
+
},
|
|
87
|
+
'FormModelContextParameterMissingTitleAndDescription': {
|
|
88
|
+
severity: Severity.WARNING,
|
|
89
|
+
description: str_(UIStrings.missingParamDescription),
|
|
90
|
+
},
|
|
91
|
+
'FormModelContextParameterMissingName': {
|
|
92
|
+
severity: Severity.WARNING,
|
|
93
|
+
description: str_(UIStrings.missingOptionalParamName),
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const rawIssues = artifacts.WebMcpSchemaIssues;
|
|
98
|
+
|
|
99
|
+
const uniqueIssues = [
|
|
100
|
+
...new Map(
|
|
101
|
+
rawIssues.map(issue => [`${issue.violatingNodeId}_${issue.errorType}`, issue])
|
|
102
|
+
).values(),
|
|
103
|
+
];
|
|
104
|
+
const sortedUniqueIssues = uniqueIssues.sort((a, b) => {
|
|
105
|
+
return (issueConfigs[a.errorType]?.severity || Severity.ERROR) -
|
|
106
|
+
(issueConfigs[b.errorType]?.severity || Severity.ERROR);
|
|
107
|
+
});
|
|
108
|
+
const items = sortedUniqueIssues.map(issue => {
|
|
109
|
+
return {
|
|
110
|
+
element: issue.nodeDetails ? Audit.makeNodeItem(issue.nodeDetails) : undefined,
|
|
111
|
+
issue: issueConfigs[issue.errorType]?.description || '',
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
116
|
+
const headings = [
|
|
117
|
+
{key: 'element', valueType: 'node', label: str_(UIStrings.columnElement)},
|
|
118
|
+
{key: 'issue', valueType: 'text', label: str_(UIStrings.columnIssue)},
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const details = Audit.makeTableDetails(headings, items);
|
|
122
|
+
|
|
123
|
+
const hasErrors =
|
|
124
|
+
sortedUniqueIssues.some(issue => issueConfigs[issue.errorType]?.severity === Severity.ERROR);
|
|
125
|
+
|
|
126
|
+
if ((artifacts.WebMCP.tools?.length || 0) === 0 && rawIssues.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
notApplicable: true,
|
|
129
|
+
score: 1,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
score: hasErrors ? 0 : (items.length > 0 ? 0.5 : 1),
|
|
135
|
+
details: items.length > 0 ? details : undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default WebMcpSchemaValidity;
|
|
141
|
+
export {UIStrings};
|
|
@@ -42,7 +42,6 @@ class DocumentUrls {
|
|
|
42
42
|
if (!requestedUrl || !mainDocumentUrl) throw new Error('No main frame navigations found');
|
|
43
43
|
|
|
44
44
|
const initialRequest = Lantern.Core.NetworkAnalyzer.findResourceForUrl(
|
|
45
|
-
// @ts-expect-error - trace engine types for InitiatorType are outdated
|
|
46
45
|
networkRecords,
|
|
47
46
|
requestedUrl
|
|
48
47
|
);
|
|
@@ -29,7 +29,6 @@ class MainResource {
|
|
|
29
29
|
// would have evicted the first request by the time `MainDocumentRequest` (a consumer
|
|
30
30
|
// of this computed artifact) attempts to fetch the contents, resulting in a protocol error.
|
|
31
31
|
const mainResource = Lantern.Core.NetworkAnalyzer.findLastDocumentForUrl(
|
|
32
|
-
// @ts-expect-error - trace engine types for InitiatorType are outdated
|
|
33
32
|
records,
|
|
34
33
|
mainDocumentUrl
|
|
35
34
|
);
|
|
@@ -37,7 +36,6 @@ class MainResource {
|
|
|
37
36
|
throw new Error('Unable to identify the main resource');
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
// @ts-expect-error - Return type is typed as Lantern request by trace engine, but it is a raw record at runtime since we passed raw records.
|
|
41
39
|
return mainResource;
|
|
42
40
|
}
|
|
43
41
|
}
|
|
@@ -39,14 +39,14 @@ async function getComputationDataParamsFromTrace(data, context) {
|
|
|
39
39
|
const graph = await PageDependencyGraph.request({...data, fromTrace: true}, context);
|
|
40
40
|
const traceEngineResult = await TraceEngineResult.request(data, context);
|
|
41
41
|
const frameId = traceEngineResult.data.Meta.mainFrameId;
|
|
42
|
-
const
|
|
43
|
-
traceEngineResult.data.Meta.mainFrameNavigations[0]
|
|
44
|
-
if (!
|
|
42
|
+
const navigation =
|
|
43
|
+
traceEngineResult.data.Meta.mainFrameNavigations[0];
|
|
44
|
+
if (!navigation) {
|
|
45
45
|
throw new Error(`Lantern metrics could not be calculated due to missing navigation id`);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const processedNavigation = Lantern.TraceEngineComputationData.createProcessedNavigation(
|
|
49
|
-
traceEngineResult.data, frameId,
|
|
49
|
+
traceEngineResult.data, frameId, navigation);
|
|
50
50
|
const simulator = data.simulator || (await LoadSimulator.request(data, context));
|
|
51
51
|
|
|
52
52
|
return {simulator, graph, processedNavigation};
|
|
@@ -62,7 +62,7 @@ class LCPBreakdown {
|
|
|
62
62
|
throw new LighthouseError(LighthouseError.errors.NO_LCP, {}, {cause: lcpBreakdown});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if (!lcpBreakdown.subparts) {
|
|
65
|
+
if (!lcpBreakdown || !lcpBreakdown.subparts) {
|
|
66
66
|
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -53,7 +53,7 @@ class TimeToFirstByte extends NavigationMetric {
|
|
|
53
53
|
const lcpBreakdown = navInsights.model.LCPBreakdown;
|
|
54
54
|
|
|
55
55
|
// Defer to LCP breakdown, but if there's no LCP fallback to manual calculation.
|
|
56
|
-
if (!(lcpBreakdown instanceof Error) && lcpBreakdown.subparts) {
|
|
56
|
+
if (lcpBreakdown && !(lcpBreakdown instanceof Error) && lcpBreakdown.subparts) {
|
|
57
57
|
return {
|
|
58
58
|
timing: lcpBreakdown.subparts.ttfb.range / 1000,
|
|
59
59
|
timestamp: lcpBreakdown.subparts.ttfb.max,
|
|
@@ -26,7 +26,8 @@ class NavigationInsights {
|
|
|
26
26
|
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
27
27
|
if (!navigationId) throw new Error('No navigationId found');
|
|
28
28
|
|
|
29
|
-
const navInsights = traceEngineResult.insights.
|
|
29
|
+
const navInsights = [...traceEngineResult.insights.values()]
|
|
30
|
+
.find(insightSet => insightSet.navigation?.args.data?.navigationId === navigationId);
|
|
30
31
|
if (!navInsights) throw new Error('No navigations insights found');
|
|
31
32
|
|
|
32
33
|
return navInsights;
|
|
@@ -19,7 +19,6 @@ class NetworkAnalysis {
|
|
|
19
19
|
static async compute_(devtoolsLog, context) {
|
|
20
20
|
const records = await NetworkRecords.request(devtoolsLog, context);
|
|
21
21
|
const analysis = Lantern.Core.NetworkAnalyzer.analyze(
|
|
22
|
-
// @ts-expect-error - trace engine types for InitiatorType are outdated
|
|
23
22
|
records
|
|
24
23
|
);
|
|
25
24
|
if (!analysis) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default config;
|
|
2
|
+
/** @type {LH.Config} */
|
|
3
|
+
declare const config: LH.Config;
|
|
4
|
+
export namespace UIStrings {
|
|
5
|
+
let agenticBrowsingCategoryTitle: string;
|
|
6
|
+
let agenticBrowsingCategoryDescription: string;
|
|
7
|
+
let webmcpGroupTitle: string;
|
|
8
|
+
let webmcpGroupDescription: string;
|
|
9
|
+
let agentAccessibilityGroupTitle: string;
|
|
10
|
+
let agentAccessibilityGroupDescription: string;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=agentic-browsing-config.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
8
|
+
|
|
9
|
+
const UIStrings = {
|
|
10
|
+
/** Title of the Agentic Browsing category of audits. */
|
|
11
|
+
agenticBrowsingCategoryTitle: 'Agentic Browsing',
|
|
12
|
+
/** Description of the Agentic Browsing category. */
|
|
13
|
+
agenticBrowsingCategoryDescription: 'These checks ensure high-quality, browsable ' +
|
|
14
|
+
'websites for AI agents and validate the correctness of WebMCP integrations. ' +
|
|
15
|
+
'This category is still under development and subject to change.',
|
|
16
|
+
/** Title of the WebMCP group of audits. */
|
|
17
|
+
webmcpGroupTitle: 'WebMCP',
|
|
18
|
+
/** Description of the WebMCP group. */
|
|
19
|
+
webmcpGroupDescription: 'Audits validating WebMCP integration.',
|
|
20
|
+
/** Title of the Agent Accessibility group of audits. */
|
|
21
|
+
agentAccessibilityGroupTitle: 'Agent Accessibility',
|
|
22
|
+
/** Description of the Agent Accessibility group of audits. */
|
|
23
|
+
agentAccessibilityGroupDescription: 'These audits highlight best practices for improving the ' +
|
|
24
|
+
'accessibility of the website for AI agents.',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
28
|
+
|
|
29
|
+
/** @type {LH.Config} */
|
|
30
|
+
const config = {
|
|
31
|
+
extends: 'lighthouse:default',
|
|
32
|
+
audits: [
|
|
33
|
+
'agentic/agent-accessibility-tree',
|
|
34
|
+
'webmcp-registered-tools',
|
|
35
|
+
'webmcp-form-coverage',
|
|
36
|
+
'webmcp-schema-validity',
|
|
37
|
+
'agentic/llms-txt',
|
|
38
|
+
],
|
|
39
|
+
artifacts: [
|
|
40
|
+
{id: 'WebMCP', gatherer: 'webmcp'},
|
|
41
|
+
{id: 'WebMcpSchemaIssues', gatherer: 'webmcp-schema'},
|
|
42
|
+
{id: 'LlmsTxt', gatherer: 'agentic/llms-txt'},
|
|
43
|
+
],
|
|
44
|
+
groups: {
|
|
45
|
+
'webmcp': {
|
|
46
|
+
title: str_(UIStrings.webmcpGroupTitle),
|
|
47
|
+
description: str_(UIStrings.webmcpGroupDescription),
|
|
48
|
+
},
|
|
49
|
+
'agent-accessibility': {
|
|
50
|
+
title: str_(UIStrings.agentAccessibilityGroupTitle),
|
|
51
|
+
description: str_(UIStrings.agentAccessibilityGroupDescription),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
categories: {
|
|
55
|
+
'agentic-browsing': {
|
|
56
|
+
title: str_(UIStrings.agenticBrowsingCategoryTitle),
|
|
57
|
+
description: str_(UIStrings.agenticBrowsingCategoryDescription),
|
|
58
|
+
supportedModes: ['navigation', 'snapshot'],
|
|
59
|
+
categoryScoreDisplayMode: 'fraction',
|
|
60
|
+
auditRefs: [
|
|
61
|
+
{id: 'agent-accessibility-tree', weight: 1, group: 'agent-accessibility'},
|
|
62
|
+
{id: 'webmcp-form-coverage', weight: 1, group: 'webmcp'},
|
|
63
|
+
{id: 'webmcp-registered-tools', weight: 1, group: 'webmcp'},
|
|
64
|
+
{id: 'webmcp-schema-validity', weight: 1, group: 'webmcp'},
|
|
65
|
+
{id: 'cumulative-layout-shift', weight: 1, acronym: 'CLS'},
|
|
66
|
+
{id: 'llms-txt', weight: 1, group: 'agent-accessibility'},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default config;
|
|
73
|
+
export {UIStrings};
|
|
@@ -254,6 +254,9 @@ const defaultConfig = {
|
|
|
254
254
|
'accessibility/manual/offscreen-content-hidden',
|
|
255
255
|
'accessibility/manual/use-landmarks',
|
|
256
256
|
'accessibility/manual/visual-order-follows-dom',
|
|
257
|
+
'accessibility/autocomplete-valid',
|
|
258
|
+
'accessibility/presentation-role-conflict',
|
|
259
|
+
'accessibility/svg-img-alt',
|
|
257
260
|
'byte-efficiency/total-byte-weight',
|
|
258
261
|
'byte-efficiency/unminified-css',
|
|
259
262
|
'byte-efficiency/unminified-javascript',
|
|
@@ -515,6 +518,9 @@ const defaultConfig = {
|
|
|
515
518
|
{id: 'valid-lang', weight: 7, group: 'a11y-language'}, // Serious, wcag2aa
|
|
516
519
|
{id: 'video-caption', weight: 10, group: 'a11y-audio-video'}, // Critical, wcag2a
|
|
517
520
|
{id: 'landmark-one-main', weight: 3, group: 'a11y-best-practices'}, // Moderate, best-practice
|
|
521
|
+
{id: 'autocomplete-valid', weight: 1, group: 'a11y-best-practices'}, // Informational
|
|
522
|
+
{id: 'presentation-role-conflict', weight: 1, group: 'a11y-best-practices'}, // Informational
|
|
523
|
+
{id: 'svg-img-alt', weight: 1, group: 'a11y-best-practices'}, // Informational
|
|
518
524
|
// Manual audits
|
|
519
525
|
{id: 'focusable-controls', weight: 0},
|
|
520
526
|
{id: 'interactive-element-affordance', weight: 0},
|
|
@@ -50,6 +50,7 @@ async function runA11yChecks() {
|
|
|
50
50
|
'aria-roledescription': {enabled: false},
|
|
51
51
|
'aria-treeitem-name': {enabled: true},
|
|
52
52
|
'aria-text': {enabled: true},
|
|
53
|
+
'autocomplete-valid': {enabled: true},
|
|
53
54
|
'audio-caption': {enabled: false},
|
|
54
55
|
'blink': {enabled: false},
|
|
55
56
|
'duplicate-id': {enabled: false},
|
|
@@ -69,6 +70,7 @@ async function runA11yChecks() {
|
|
|
69
70
|
// https://github.com/dequelabs/axe-core/issues/2958
|
|
70
71
|
'nested-interactive': {enabled: false},
|
|
71
72
|
'no-autoplay-audio': {enabled: false},
|
|
73
|
+
'presentation-role-conflict': {enabled: true},
|
|
72
74
|
'role-img-alt': {enabled: false},
|
|
73
75
|
'scrollable-region-focusable': {enabled: false},
|
|
74
76
|
'select-name': {enabled: true},
|
|
@@ -76,7 +78,7 @@ async function runA11yChecks() {
|
|
|
76
78
|
'skip-link': {enabled: true},
|
|
77
79
|
// https://github.com/GoogleChrome/lighthouse/issues/16163
|
|
78
80
|
'summary-name': {enabled: false},
|
|
79
|
-
'svg-img-alt': {enabled:
|
|
81
|
+
'svg-img-alt': {enabled: true},
|
|
80
82
|
'tabindex': {enabled: true},
|
|
81
83
|
'table-duplicate-name': {enabled: true},
|
|
82
84
|
'table-fake-caption': {enabled: true},
|
|
@@ -176,6 +178,8 @@ function createAxeRuleResultArtifact(result) {
|
|
|
176
178
|
tags: result.tags,
|
|
177
179
|
nodes,
|
|
178
180
|
error,
|
|
181
|
+
help: result.help,
|
|
182
|
+
description: result.description,
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
185
|
/* c8 ignore stop */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default LlmsTxt;
|
|
2
|
+
declare class LlmsTxt extends BaseGatherer {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Gatherer.Context} passContext
|
|
5
|
+
* @return {Promise<LH.Artifacts['LlmsTxt']>}
|
|
6
|
+
*/
|
|
7
|
+
getArtifact(passContext: LH.Gatherer.Context): Promise<LH.Artifacts["LlmsTxt"]>;
|
|
8
|
+
}
|
|
9
|
+
import BaseGatherer from '../../base-gatherer.js';
|
|
10
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import BaseGatherer from '../../base-gatherer.js';
|
|
9
|
+
|
|
10
|
+
class LlmsTxt extends BaseGatherer {
|
|
11
|
+
/** @type {LH.Gatherer.GathererMeta} */
|
|
12
|
+
meta = {
|
|
13
|
+
supportedModes: ['snapshot', 'navigation'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {LH.Gatherer.Context} passContext
|
|
18
|
+
* @return {Promise<LH.Artifacts['LlmsTxt']>}
|
|
19
|
+
*/
|
|
20
|
+
async getArtifact(passContext) {
|
|
21
|
+
const {finalDisplayedUrl} = passContext.baseArtifacts.URL;
|
|
22
|
+
const llmUrl = new URL('/llms.txt', finalDisplayedUrl).href;
|
|
23
|
+
return passContext.driver.fetcher.fetchResource(llmUrl)
|
|
24
|
+
.catch(err => ({status: null, content: null, errorMessage: err.message}));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default LlmsTxt;
|
|
@@ -33,6 +33,8 @@ function collectElements() {
|
|
|
33
33
|
autocomplete: formEl.autocomplete,
|
|
34
34
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
35
35
|
node: getNodeDetails(formEl),
|
|
36
|
+
webMcpToolname: formEl.getAttribute('toolname'),
|
|
37
|
+
webMcpTooldescription: formEl.getAttribute('tooldescription'),
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -32,7 +32,7 @@ function collectMetaElements() {
|
|
|
32
32
|
property: getAttribute('property'),
|
|
33
33
|
httpEquiv: meta.httpEquiv ? meta.httpEquiv.toLowerCase() : undefined,
|
|
34
34
|
charset: getAttribute('charset'),
|
|
35
|
-
node: functions.getNodeDetails(meta),
|
|
35
|
+
node: /** @type {LH.Artifacts.NodeDetails} */ (functions.getNodeDetails(meta)),
|
|
36
36
|
};
|
|
37
37
|
});
|
|
38
38
|
}
|
|
@@ -142,7 +142,7 @@ class TraceElements extends BaseGatherer {
|
|
|
142
142
|
}, new Set());
|
|
143
143
|
|
|
144
144
|
// TODO: handle digging into Map in recursiveObjectEnumerate.
|
|
145
|
-
for (const shift of insightSet.model.CLSCulprits
|
|
145
|
+
for (const shift of insightSet.model.CLSCulprits?.shifts.values() ?? []) {
|
|
146
146
|
nodeIds.push(...shift.unsizedImages.map(s => s.backendNodeId));
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default WebMcpSchemaIssues;
|
|
2
|
+
declare class WebMcpSchemaIssues extends BaseGatherer {
|
|
3
|
+
/** @type {LH.Artifacts.WebMcpSchemaIssue[]} */
|
|
4
|
+
_issues: LH.Artifacts.WebMcpSchemaIssue[];
|
|
5
|
+
_onIssueAdded: (event: Record<string, any>) => void;
|
|
6
|
+
/**
|
|
7
|
+
* @param {Record<string, any>} event
|
|
8
|
+
*/
|
|
9
|
+
onIssueAdded(event: Record<string, any>): void;
|
|
10
|
+
/**
|
|
11
|
+
* @param {LH.Gatherer.Context} passContext
|
|
12
|
+
*/
|
|
13
|
+
startInstrumentation(passContext: LH.Gatherer.Context): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* @param {LH.Gatherer.Context} passContext
|
|
16
|
+
*/
|
|
17
|
+
stopInstrumentation(passContext: LH.Gatherer.Context): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* @param {LH.Gatherer.Context} context
|
|
20
|
+
* @return {Promise<LH.Artifacts.WebMcpSchemaIssue[]>}
|
|
21
|
+
*/
|
|
22
|
+
getArtifact(context: LH.Gatherer.Context): Promise<LH.Artifacts.WebMcpSchemaIssue[]>;
|
|
23
|
+
}
|
|
24
|
+
import BaseGatherer from '../base-gatherer.js';
|
|
25
|
+
//# sourceMappingURL=webmcp-schema.d.ts.map
|