lighthouse 13.1.0 → 13.3.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/cli/cli-flags.js +2 -2
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +7 -3
- package/cli/test/smokehouse/lighthouse-runners/cli.js +5 -1
- package/cli/test/smokehouse/lighthouse-runners/devtools-mcp.js +7 -3
- package/cli/test/smokehouse/lighthouse-runners/devtools.js +4 -1
- package/cli/test/smokehouse/smokehouse.js +7 -2
- 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 +51 -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/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/cdt/generated/SourceMap.js +2 -2
- package/core/lib/deprecations-strings.d.ts +26 -20
- package/core/lib/deprecations-strings.js +7 -0
- 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 +263 -17
- package/shared/localization/locales/en-XL.json +263 -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/internal/smokehouse.d.ts +7 -1
- package/types/lhr/lhr.d.ts +11 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default WebMcpFormCoverage;
|
|
2
|
+
declare class WebMcpFormCoverage 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 displayValue: string;
|
|
13
|
+
let columnForm: string;
|
|
14
|
+
}
|
|
15
|
+
import { Audit } from './audit.js';
|
|
16
|
+
//# sourceMappingURL=webmcp-form-coverage.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
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 lists forms found in the page for WebMCP coverage. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
12
|
+
title: 'WebMCP form coverage',
|
|
13
|
+
/** Description of a Lighthouse audit that lists forms found in the page and indicates whether they have WebMCP declarative tool annotations. 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. */
|
|
14
|
+
description: 'Consider adding [WebMCP](http://goo.gle/webmcp-docs) annotations to the forms listed below. This helps AI ' +
|
|
15
|
+
'agents identify and interact with these forms more reliably.',
|
|
16
|
+
/** [ICU Syntax] Label for the audit identifying the number of forms missing annotations. "WebMCP" stands for "Web Model Context Protocol" and should not be translated. */
|
|
17
|
+
displayValue: `{itemCount, plural,
|
|
18
|
+
=1 {1 form missing annotations}
|
|
19
|
+
other {# forms missing annotations}
|
|
20
|
+
}`,
|
|
21
|
+
/** Label for a column in a data table; entries will be the form element found on the page. */
|
|
22
|
+
columnForm: 'Form',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
26
|
+
|
|
27
|
+
class WebMcpFormCoverage extends Audit {
|
|
28
|
+
/**
|
|
29
|
+
* @return {LH.Audit.Meta}
|
|
30
|
+
*/
|
|
31
|
+
static get meta() {
|
|
32
|
+
return {
|
|
33
|
+
id: 'webmcp-form-coverage',
|
|
34
|
+
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
35
|
+
title: str_(UIStrings.title),
|
|
36
|
+
description: str_(UIStrings.description),
|
|
37
|
+
requiredArtifacts: ['Inputs', 'WebMCP'],
|
|
38
|
+
supportedModes: ['navigation', 'snapshot'],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {LH.Artifacts} artifacts
|
|
44
|
+
* @return {LH.Audit.Product}
|
|
45
|
+
*/
|
|
46
|
+
static audit(artifacts) {
|
|
47
|
+
const forms = artifacts.Inputs.forms;
|
|
48
|
+
if (forms.length === 0 || !artifacts.WebMCP.isSupported) {
|
|
49
|
+
return {
|
|
50
|
+
notApplicable: true,
|
|
51
|
+
score: 1,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const withoutTools = [];
|
|
55
|
+
|
|
56
|
+
for (const form of forms) {
|
|
57
|
+
const hasToolName = !!form.webMcpToolname;
|
|
58
|
+
const hasToolDescription = !!form.webMcpTooldescription;
|
|
59
|
+
|
|
60
|
+
if (!hasToolName && !hasToolDescription) {
|
|
61
|
+
withoutTools.push({
|
|
62
|
+
node: Audit.makeNodeItem(form.node),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (withoutTools.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
notApplicable: true,
|
|
70
|
+
score: 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
75
|
+
const headings = [
|
|
76
|
+
{key: 'node', valueType: 'node', label: str_(UIStrings.columnForm)},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const details = Audit.makeTableDetails(headings, withoutTools);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
score: 1,
|
|
83
|
+
displayValue: str_(UIStrings.displayValue, {itemCount: withoutTools.length}),
|
|
84
|
+
details,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default WebMcpFormCoverage;
|
|
90
|
+
export {UIStrings};
|
|
@@ -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](http://goo.gle/webmcp-docs) 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](http://goo.gle/webmcp-docs) 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 websites for AI agents](https://goo.gle/lighthouse-agentic-web) ' +
|
|
14
|
+
'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};
|