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
package/cli/bin.js
CHANGED
|
@@ -107,6 +107,11 @@ async function begin() {
|
|
|
107
107
|
cliFlags.precomputedLanternData = data;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
if (!Array.isArray(cliFlags.chromeFlags)) {
|
|
111
|
+
cliFlags.chromeFlags = [cliFlags.chromeFlags];
|
|
112
|
+
}
|
|
113
|
+
cliFlags.chromeFlags.push('--enable-features=DevToolsWebMCPSupport');
|
|
114
|
+
|
|
110
115
|
// By default, cliFlags.enableErrorReporting is undefined so the user is
|
|
111
116
|
// prompted. This can be overridden with an explicit flag or by the cached
|
|
112
117
|
// answer returned by askPermission().
|
package/cli/cli-flags.js
CHANGED
|
@@ -54,7 +54,7 @@ function getYargsParser(manualArgv) {
|
|
|
54
54
|
'Path to JSON file of HTTP Header key/value pairs to send in requests')
|
|
55
55
|
.example(
|
|
56
56
|
'lighthouse <url> --only-categories=performance,seo',
|
|
57
|
-
'Only run the specified categories. Available categories: accessibility, best-practices, performance, seo')
|
|
57
|
+
'Only run the specified categories. Available categories: accessibility, best-practices, performance, seo, agentic-browsing')
|
|
58
58
|
|
|
59
59
|
// We only have the single string positional argument, the url.
|
|
60
60
|
.option('_', {
|
|
@@ -188,7 +188,7 @@ function getYargsParser(manualArgv) {
|
|
|
188
188
|
array: true,
|
|
189
189
|
type: 'string',
|
|
190
190
|
coerce: splitCommaSeparatedValues,
|
|
191
|
-
describe: 'Only run the specified categories. Available categories: accessibility, best-practices, performance, seo',
|
|
191
|
+
describe: 'Only run the specified categories. Available categories: accessibility, best-practices, performance, seo, agentic-browsing',
|
|
192
192
|
},
|
|
193
193
|
'skip-audits': {
|
|
194
194
|
array: true,
|
|
@@ -80,11 +80,15 @@ async function runBundledLighthouse(url, config, testRunnerOptions) {
|
|
|
80
80
|
const thirdPartyWeb = global.thirdPartyWeb;
|
|
81
81
|
thirdPartyWeb.provideThirdPartyWeb(thirdPartyWebLib);
|
|
82
82
|
|
|
83
|
+
const chromeFlags = [];
|
|
84
|
+
if (testRunnerOptions?.headless) chromeFlags.push('--headless=new');
|
|
85
|
+
if (testRunnerOptions?.chromeFlags) {
|
|
86
|
+
chromeFlags.push(...testRunnerOptions.chromeFlags.split(' '));
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
// Launch and connect to Chrome.
|
|
84
90
|
const launchedChrome = await ChromeLauncher.launch({
|
|
85
|
-
chromeFlags
|
|
86
|
-
testRunnerOptions?.headless ? '--headless=new' : '',
|
|
87
|
-
],
|
|
91
|
+
chromeFlags,
|
|
88
92
|
});
|
|
89
93
|
const port = launchedChrome.port;
|
|
90
94
|
|
|
@@ -66,7 +66,11 @@ async function internalRun(url, tmpPath, config, logger, options) {
|
|
|
66
66
|
'--quiet',
|
|
67
67
|
];
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
let chromeFlags = '';
|
|
70
|
+
if (headless) chromeFlags += '--headless=new ';
|
|
71
|
+
if (options && options.chromeFlags) chromeFlags += options.chromeFlags;
|
|
72
|
+
|
|
73
|
+
if (chromeFlags) args.push(`--chrome-flags="${chromeFlags.trim()}"`);
|
|
70
74
|
|
|
71
75
|
// Config can be optionally provided.
|
|
72
76
|
if (config) {
|
|
@@ -56,11 +56,15 @@ async function runBundledLighthouse(url, config, testRunnerOptions) {
|
|
|
56
56
|
// Load bundle.
|
|
57
57
|
const {navigation} = await import(LH_ROOT + '/dist/lighthouse-devtools-mcp-bundle.js');
|
|
58
58
|
|
|
59
|
+
const chromeFlags = [];
|
|
60
|
+
if (testRunnerOptions?.headless) chromeFlags.push('--headless=new');
|
|
61
|
+
if (testRunnerOptions?.chromeFlags) {
|
|
62
|
+
chromeFlags.push(...testRunnerOptions.chromeFlags.split(' '));
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
// Launch and connect to Chrome.
|
|
60
66
|
const launchedChrome = await ChromeLauncher.launch({
|
|
61
|
-
chromeFlags
|
|
62
|
-
testRunnerOptions?.headless ? '--headless=new' : '',
|
|
63
|
-
],
|
|
67
|
+
chromeFlags,
|
|
64
68
|
});
|
|
65
69
|
const port = launchedChrome.port;
|
|
66
70
|
|
|
@@ -46,9 +46,12 @@ async function setup() {
|
|
|
46
46
|
*/
|
|
47
47
|
async function runLighthouse(url, config, logger, testRunnerOptions) {
|
|
48
48
|
const chromeFlags = [
|
|
49
|
-
testRunnerOptions?.headless ? '--headless=new' : '',
|
|
50
49
|
`--custom-devtools-frontend=file://${devtoolsDir}/out/LighthouseIntegration/gen/front_end`,
|
|
51
50
|
];
|
|
51
|
+
if (testRunnerOptions?.headless) chromeFlags.push('--headless=new');
|
|
52
|
+
if (testRunnerOptions?.chromeFlags) {
|
|
53
|
+
chromeFlags.push(...testRunnerOptions.chromeFlags.split(' '));
|
|
54
|
+
}
|
|
52
55
|
// TODO: `testUrlFromDevtools` should accept a logger, so we get some output even for time outs.
|
|
53
56
|
const {lhr, artifacts, logs} = await testUrlFromDevtools(url, {
|
|
54
57
|
config,
|
|
@@ -136,7 +136,7 @@ function purpleify(str) {
|
|
|
136
136
|
* @return {Promise<SmokehouseResult>}
|
|
137
137
|
*/
|
|
138
138
|
async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
139
|
-
const {id, expectations, config} = smokeTestDefn;
|
|
139
|
+
const {id, expectations, config, testRunnerOptions: customTestRunnerOptions} = smokeTestDefn;
|
|
140
140
|
const {
|
|
141
141
|
lighthouseRunner,
|
|
142
142
|
retries,
|
|
@@ -145,6 +145,11 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
145
145
|
} = testOptions;
|
|
146
146
|
const requestedUrl = expectations.lhr.requestedUrl;
|
|
147
147
|
|
|
148
|
+
const mergedTestRunnerOptions = {
|
|
149
|
+
...testRunnerOptions,
|
|
150
|
+
...customTestRunnerOptions,
|
|
151
|
+
};
|
|
152
|
+
|
|
148
153
|
console.log(`${purpleify(id)} smoketest starting…`);
|
|
149
154
|
|
|
150
155
|
// Rerun test until there's a passing result or retries are exhausted to prevent flakes.
|
|
@@ -170,7 +175,7 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
170
175
|
reject(new Error('Timed out waiting for provided lighthouseRunner')), 1000 * 120);
|
|
171
176
|
});
|
|
172
177
|
const timedResult = await Promise.race([
|
|
173
|
-
lighthouseRunner(requestedUrl, config, logger,
|
|
178
|
+
lighthouseRunner(requestedUrl, config, logger, mergedTestRunnerOptions),
|
|
174
179
|
timeoutPromise,
|
|
175
180
|
]);
|
|
176
181
|
result = {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default AutocompleteValid;
|
|
2
|
+
declare class AutocompleteValid extends AxeAudit {
|
|
3
|
+
}
|
|
4
|
+
export namespace UIStrings {
|
|
5
|
+
let title: string;
|
|
6
|
+
let failureTitle: string;
|
|
7
|
+
let description: string;
|
|
8
|
+
}
|
|
9
|
+
import AxeAudit from './axe-audit.js';
|
|
10
|
+
//# sourceMappingURL=autocomplete-valid.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Ensures the autocomplete attribute is correct and suitable for the form field.
|
|
9
|
+
* See base class in axe-audit.js for audit() implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import AxeAudit from './axe-audit.js';
|
|
13
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
|
+
|
|
15
|
+
const UIStrings = {
|
|
16
|
+
/** Title of an accessibility audit that evaluates if all form fields have valid autocomplete attributes. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
17
|
+
title: '`autocomplete` attributes are used correctly',
|
|
18
|
+
/** Title of an accessibility audit that evaluates if all form fields have valid autocomplete attributes. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
|
|
19
|
+
failureTitle: '`autocomplete` attributes are not used correctly',
|
|
20
|
+
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
21
|
+
description: 'The `autocomplete` attribute values must be valid and correctly applied for ' +
|
|
22
|
+
'screen readers to function correctly. ' +
|
|
23
|
+
'[Learn more about valid autocomplete values](https://dequeuniversity.com/rules/axe/4.11/autocomplete-valid).',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
27
|
+
|
|
28
|
+
class AutocompleteValid extends AxeAudit {
|
|
29
|
+
/**
|
|
30
|
+
* @return {LH.Audit.Meta}
|
|
31
|
+
*/
|
|
32
|
+
static get meta() {
|
|
33
|
+
return {
|
|
34
|
+
id: 'autocomplete-valid',
|
|
35
|
+
title: str_(UIStrings.title),
|
|
36
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
37
|
+
description: str_(UIStrings.description),
|
|
38
|
+
requiredArtifacts: ['Accessibility'],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default AutocompleteValid;
|
|
44
|
+
export {UIStrings};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default PresentationRoleConflict;
|
|
2
|
+
declare class PresentationRoleConflict extends AxeAudit {
|
|
3
|
+
}
|
|
4
|
+
export namespace UIStrings {
|
|
5
|
+
let title: string;
|
|
6
|
+
let failureTitle: string;
|
|
7
|
+
let description: string;
|
|
8
|
+
}
|
|
9
|
+
import AxeAudit from './axe-audit.js';
|
|
10
|
+
//# sourceMappingURL=presentation-role-conflict.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Ensures elements which are marked to be removed from the accessibility tree are in fact removed.
|
|
9
|
+
* See base class in axe-audit.js for audit() implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import AxeAudit from './axe-audit.js';
|
|
13
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
|
+
|
|
15
|
+
const UIStrings = {
|
|
16
|
+
/** Title of an accessibility audit that evaluates if elements with presentation role have conflicts. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
17
|
+
title: 'Elements with `role="none"` or `role="presentation"` do not have conflicts',
|
|
18
|
+
/** Title of an accessibility audit that evaluates if elements with presentation role have conflicts. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
|
|
19
|
+
failureTitle: 'Elements with `role="none"` or `role="presentation"` have conflicts',
|
|
20
|
+
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with \'Learn\' becomes link text to additional documentation. */
|
|
21
|
+
description: 'There are certain cases where the semantic role of an element with `role="none"` ' +
|
|
22
|
+
'or `role="presentation"` does not resolve to none or presentation. To ensure the ' +
|
|
23
|
+
'element remains removed from the accessibility tree, you should not add any global ' +
|
|
24
|
+
'ARIA attributes to the element or make it focusable. ' +
|
|
25
|
+
'[Learn more about presentation role conflict](https://dequeuniversity.com/rules/axe/4.11/presentation-role-conflict).',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
29
|
+
|
|
30
|
+
class PresentationRoleConflict extends AxeAudit {
|
|
31
|
+
/**
|
|
32
|
+
* @return {LH.Audit.Meta}
|
|
33
|
+
*/
|
|
34
|
+
static get meta() {
|
|
35
|
+
return {
|
|
36
|
+
id: 'presentation-role-conflict',
|
|
37
|
+
title: str_(UIStrings.title),
|
|
38
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
39
|
+
description: str_(UIStrings.description),
|
|
40
|
+
requiredArtifacts: ['Accessibility'],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default PresentationRoleConflict;
|
|
46
|
+
export {UIStrings};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default SvgImgAlt;
|
|
2
|
+
declare class SvgImgAlt extends AxeAudit {
|
|
3
|
+
}
|
|
4
|
+
export namespace UIStrings {
|
|
5
|
+
let title: string;
|
|
6
|
+
let failureTitle: string;
|
|
7
|
+
let description: string;
|
|
8
|
+
}
|
|
9
|
+
import AxeAudit from './axe-audit.js';
|
|
10
|
+
//# sourceMappingURL=svg-img-alt.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Ensures SVG elements with an img role have an accessible text alternative.
|
|
9
|
+
* See base class in axe-audit.js for audit() implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import AxeAudit from './axe-audit.js';
|
|
13
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
|
+
|
|
15
|
+
const UIStrings = {
|
|
16
|
+
/** Title of an accessibility audit that evaluates if SVG elements with an img role have an accessible text alternative. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
17
|
+
title: 'SVG elements with an `img` role have an accessible text alternative',
|
|
18
|
+
/** Title of an accessibility audit that evaluates if SVG elements with an img role have an accessible text alternative. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
|
|
19
|
+
failureTitle: 'SVG elements with an `img` role do not have an accessible text alternative',
|
|
20
|
+
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with \'Learn\' becomes link text to additional documentation. */
|
|
21
|
+
description: 'Ensures SVG elements with an `img`, `graphics-document` or `graphics-symbol` ' +
|
|
22
|
+
'role have an accessible text alternative. ' +
|
|
23
|
+
'[Learn more about SVG alt text](https://dequeuniversity.com/rules/axe/4.11/svg-img-alt).',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
27
|
+
|
|
28
|
+
class SvgImgAlt extends AxeAudit {
|
|
29
|
+
/**
|
|
30
|
+
* @return {LH.Audit.Meta}
|
|
31
|
+
*/
|
|
32
|
+
static get meta() {
|
|
33
|
+
return {
|
|
34
|
+
id: 'svg-img-alt',
|
|
35
|
+
title: str_(UIStrings.title),
|
|
36
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
37
|
+
description: str_(UIStrings.description),
|
|
38
|
+
requiredArtifacts: ['Accessibility'],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default SvgImgAlt;
|
|
44
|
+
export {UIStrings};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default AgentAccessibilityTree;
|
|
2
|
+
declare class AgentAccessibilityTree 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 failureTitle: string;
|
|
12
|
+
let description: string;
|
|
13
|
+
let columnRule: string;
|
|
14
|
+
let columnElement: string;
|
|
15
|
+
let failedSectionTitle: string;
|
|
16
|
+
let displayValuePassed: string;
|
|
17
|
+
}
|
|
18
|
+
import { Audit } from '../audit.js';
|
|
19
|
+
//# sourceMappingURL=agent-accessibility-tree.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
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 shown when all agent accessibility checks pass. */
|
|
12
|
+
title: 'Accessibility tree is well-formed',
|
|
13
|
+
/** Title shown when one or more agent accessibility checks fail. */
|
|
14
|
+
failureTitle: 'Accessibility tree is not well-formed',
|
|
15
|
+
/** Description of a Lighthouse audit that tells the user *why* they need a well-formed accessibility tree. */
|
|
16
|
+
description: 'A well-formed [accessibility tree](http://goo.gle/lighthouse-agentic-a11y) helps AI agents to ' +
|
|
17
|
+
'navigate and interact with the page.',
|
|
18
|
+
/** Label of a table column that identifies the accessibility rule that failed. */
|
|
19
|
+
columnRule: 'Failing Rule',
|
|
20
|
+
/** Label of a table column that identifies the HTML element that failed the rule. */
|
|
21
|
+
columnElement: 'Failing Element',
|
|
22
|
+
/** Title of the section containing failed accessibility checks. */
|
|
23
|
+
failedSectionTitle: 'Failed Audits',
|
|
24
|
+
/** Message shown when all accessibility checks pass. */
|
|
25
|
+
displayValuePassed: 'All audits passed',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
29
|
+
|
|
30
|
+
const TARGET_RULES = new Set([
|
|
31
|
+
'button-name',
|
|
32
|
+
'input-button-name',
|
|
33
|
+
'input-image-alt',
|
|
34
|
+
'label',
|
|
35
|
+
'link-name',
|
|
36
|
+
'select-name',
|
|
37
|
+
'document-title',
|
|
38
|
+
'aria-allowed-attr',
|
|
39
|
+
'aria-allowed-role',
|
|
40
|
+
'aria-command-name',
|
|
41
|
+
'aria-conditional-attr',
|
|
42
|
+
'aria-dialog-name',
|
|
43
|
+
'aria-hidden-body',
|
|
44
|
+
'aria-hidden-focus',
|
|
45
|
+
'aria-input-field-name',
|
|
46
|
+
'aria-prohibited-attr',
|
|
47
|
+
'aria-required-attr',
|
|
48
|
+
'aria-required-children',
|
|
49
|
+
'aria-required-parent',
|
|
50
|
+
'aria-roles',
|
|
51
|
+
'aria-text',
|
|
52
|
+
'aria-toggle-field-name',
|
|
53
|
+
'aria-tooltip-name',
|
|
54
|
+
'aria-treeitem-name',
|
|
55
|
+
'aria-valid-attr',
|
|
56
|
+
'aria-valid-attr-value',
|
|
57
|
+
'duplicate-id-aria',
|
|
58
|
+
'definition-list',
|
|
59
|
+
'table-duplicate-name',
|
|
60
|
+
'tabindex',
|
|
61
|
+
'autocomplete-valid',
|
|
62
|
+
'presentation-role-conflict',
|
|
63
|
+
'svg-img-alt',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
class AgentAccessibilityTree extends Audit {
|
|
67
|
+
/**
|
|
68
|
+
* @return {LH.Audit.Meta}
|
|
69
|
+
*/
|
|
70
|
+
static get meta() {
|
|
71
|
+
return {
|
|
72
|
+
id: 'agent-accessibility-tree',
|
|
73
|
+
title: str_(UIStrings.title),
|
|
74
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
75
|
+
description: str_(UIStrings.description),
|
|
76
|
+
requiredArtifacts: ['Accessibility'],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {LH.Artifacts} artifacts
|
|
82
|
+
* @return {LH.Audit.Product}
|
|
83
|
+
*/
|
|
84
|
+
static audit(artifacts) {
|
|
85
|
+
const violations = (artifacts.Accessibility && artifacts.Accessibility.violations) || [];
|
|
86
|
+
const failedRules = violations.filter(v => TARGET_RULES.has(v.id));
|
|
87
|
+
|
|
88
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
89
|
+
const headings = [
|
|
90
|
+
{key: 'description', valueType: 'text', label: str_(i18n.UIStrings.columnDescription)},
|
|
91
|
+
{key: 'node', valueType: 'node', label: str_(UIStrings.columnElement)},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const items = failedRules.map(rule => ({
|
|
95
|
+
description: rule.help || rule.description,
|
|
96
|
+
node: rule.nodes?.[0] ? Audit.makeNodeItem(rule.nodes[0].node) : undefined,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const listItems = [];
|
|
100
|
+
|
|
101
|
+
if (items.length > 0) {
|
|
102
|
+
const table = Audit.makeTableDetails(headings, items);
|
|
103
|
+
listItems.push(Audit.makeListDetailSectionItem(table, str_(UIStrings.failedSectionTitle)));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
score: Number(items.length === 0),
|
|
108
|
+
details: listItems.length > 0 ? Audit.makeListDetails(listItems) : undefined,
|
|
109
|
+
displayValue: items.length === 0 ? str_(UIStrings.displayValuePassed) : undefined,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default AgentAccessibilityTree;
|
|
115
|
+
export {UIStrings};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default LlmsTxt;
|
|
2
|
+
declare class LlmsTxt 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 failureTitle: string;
|
|
12
|
+
let description: string;
|
|
13
|
+
let displayValueHttpBadCode: string;
|
|
14
|
+
let explanation: string;
|
|
15
|
+
let missingH1: string;
|
|
16
|
+
let tooShort: string;
|
|
17
|
+
let missingLinks: string;
|
|
18
|
+
}
|
|
19
|
+
import { Audit } from '../audit.js';
|
|
20
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
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 HTTP_CLIENT_ERROR_CODE_LOW = 400;
|
|
11
|
+
const HTTP_SERVER_ERROR_CODE_LOW = 500;
|
|
12
|
+
|
|
13
|
+
const UIStrings = {
|
|
14
|
+
/** Title of a Lighthouse audit that provides detail on the site's llms.txt file. Note: "llms.txt" is a canonical filename and should not be translated. This descriptive title is shown when the llms.txt file follows community recommendations. */
|
|
15
|
+
title: 'llms.txt follows recommendations',
|
|
16
|
+
/** Title of a Lighthouse audit that provides detail on the site's llms.txt file. Note: "llms.txt" is a canonical filename and should not be translated. This descriptive title is shown when the llms.txt file does not follow community recommendations. */
|
|
17
|
+
failureTitle: 'llms.txt does not follow recommendations',
|
|
18
|
+
/** Description of a Lighthouse audit that tells the user *why* they should have an llms.txt file. Note: "llms.txt" is a canonical filename and should not be translated. This is displayed after a user expands the section to see more. No character length limits. */
|
|
19
|
+
description: 'If your llms.txt file does not follow recommendations, ' +
|
|
20
|
+
'large language models may not be able to ' +
|
|
21
|
+
'understand how you want your website to be crawled or used for training. The ' +
|
|
22
|
+
'[llms.txt](https://llmstxt.org/) file should be a Markdown file containing at least one H1 header.',
|
|
23
|
+
/**
|
|
24
|
+
* @description Label for the audit identifying that the request failed with a specific HTTP status code.
|
|
25
|
+
* @example {500} statusCode
|
|
26
|
+
* */
|
|
27
|
+
displayValueHttpBadCode: 'Failed with HTTP status {statusCode}',
|
|
28
|
+
/** Explanatory message stating that there was a failure in an audit caused by Lighthouse not being able to download the llms.txt file for the site. Note: "llms.txt" is a canonical filename and should not be translated. */
|
|
29
|
+
explanation: 'Fetch of llms.txt failed',
|
|
30
|
+
/** Message indicating that the file is missing a required H1 header. */
|
|
31
|
+
missingH1: 'File is missing a required H1 header (e.g., "# Title").',
|
|
32
|
+
/** Message indicating that the file is suspiciously short. */
|
|
33
|
+
tooShort: 'File is suspiciously short.',
|
|
34
|
+
/** Message indicating that the file is missing links. */
|
|
35
|
+
missingLinks: 'File does not appear to contain any links.',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
39
|
+
|
|
40
|
+
class LlmsTxt extends Audit {
|
|
41
|
+
/**
|
|
42
|
+
* @return {LH.Audit.Meta}
|
|
43
|
+
*/
|
|
44
|
+
static get meta() {
|
|
45
|
+
return {
|
|
46
|
+
id: 'llms-txt',
|
|
47
|
+
title: str_(UIStrings.title),
|
|
48
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
49
|
+
description: str_(UIStrings.description),
|
|
50
|
+
requiredArtifacts: ['LlmsTxt'],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {LH.Artifacts} artifacts
|
|
56
|
+
* @return {LH.Audit.Product}
|
|
57
|
+
*/
|
|
58
|
+
static audit(artifacts) {
|
|
59
|
+
const {
|
|
60
|
+
status,
|
|
61
|
+
content,
|
|
62
|
+
} = artifacts.LlmsTxt;
|
|
63
|
+
|
|
64
|
+
if (!status) {
|
|
65
|
+
return {
|
|
66
|
+
score: 0,
|
|
67
|
+
explanation: str_(UIStrings.explanation),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status >= HTTP_SERVER_ERROR_CODE_LOW) {
|
|
72
|
+
return {
|
|
73
|
+
score: 0,
|
|
74
|
+
displayValue: str_(UIStrings.displayValueHttpBadCode, {statusCode: status}),
|
|
75
|
+
};
|
|
76
|
+
} else if (status >= HTTP_CLIENT_ERROR_CODE_LOW) {
|
|
77
|
+
return {
|
|
78
|
+
score: 1,
|
|
79
|
+
notApplicable: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (content === null) {
|
|
84
|
+
throw new Error(`Status ${status} was valid, but content was null`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hasH1 = /^#\s+.+/m.test(content);
|
|
88
|
+
const hasLink = /\[.+\]\(.+\)/.test(content);
|
|
89
|
+
const isTooShort = content.length < 50;
|
|
90
|
+
|
|
91
|
+
const errors = [];
|
|
92
|
+
if (!hasH1) errors.push(str_(UIStrings.missingH1));
|
|
93
|
+
if (!hasLink) errors.push(str_(UIStrings.missingLinks));
|
|
94
|
+
if (isTooShort) errors.push(str_(UIStrings.tooShort));
|
|
95
|
+
|
|
96
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
97
|
+
const headings = [
|
|
98
|
+
{key: 'message', valueType: 'text', label: 'Error'},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const details = Audit.makeTableDetails(headings, errors.map(m => ({message: m})));
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
score: Number(errors.length === 0),
|
|
105
|
+
details: errors.length ? details : undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default LlmsTxt;
|
|
111
|
+
export {UIStrings};
|
|
@@ -11,11 +11,11 @@ export type CreateDetailsExtras = {
|
|
|
11
11
|
* @param {LH.Artifacts} artifacts
|
|
12
12
|
* @param {LH.Audit.Context} context
|
|
13
13
|
* @param {T} insightName
|
|
14
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]
|
|
14
|
+
* @param {(insight: NonNullable<import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]>, extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings?: Array<string | LH.IcuMessage>, numericValue?: number, numericUnit?: LH.Audit.NumericProduct['numericUnit']}|LH.Audit.Details|undefined} createDetails
|
|
15
15
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
16
16
|
* @return {Promise<LH.Audit.Product>}
|
|
17
17
|
*/
|
|
18
|
-
export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModelsType>(artifacts: LH.Artifacts, context: LH.Audit.Context, insightName: T, createDetails: (insight: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModels[T]
|
|
18
|
+
export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModelsType>(artifacts: LH.Artifacts, context: LH.Audit.Context, insightName: T, createDetails: (insight: NonNullable<import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModels[T]>, extras: CreateDetailsExtras) => {
|
|
19
19
|
details: LH.Audit.Details;
|
|
20
20
|
warnings?: Array<string | LH.IcuMessage>;
|
|
21
21
|
numericValue?: number;
|
|
@@ -27,8 +27,10 @@ async function getInsightSet(artifacts, context) {
|
|
|
27
27
|
await TraceEngineResult.request({trace, settings, SourceMaps, HostDPR}, context);
|
|
28
28
|
|
|
29
29
|
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const insights = navigationId ?
|
|
31
|
+
[...traceEngineResult.insights.values()]
|
|
32
|
+
.find(insightSet => insightSet.navigation?.args.data?.navigationId) :
|
|
33
|
+
traceEngineResult.insights.get(NO_NAVIGATION);
|
|
32
34
|
|
|
33
35
|
return {insights, data: traceEngineResult.data};
|
|
34
36
|
}
|
|
@@ -43,7 +45,7 @@ async function getInsightSet(artifacts, context) {
|
|
|
43
45
|
* @param {LH.Artifacts} artifacts
|
|
44
46
|
* @param {LH.Audit.Context} context
|
|
45
47
|
* @param {T} insightName
|
|
46
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]
|
|
48
|
+
* @param {(insight: NonNullable<import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]>, extras: CreateDetailsExtras) => {details: LH.Audit.Details, warnings?: Array<string | LH.IcuMessage>, numericValue?: number, numericUnit?: LH.Audit.NumericProduct['numericUnit']}|LH.Audit.Details|undefined} createDetails
|
|
47
49
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
48
50
|
* @return {Promise<LH.Audit.Product>}
|
|
49
51
|
*/
|
|
@@ -56,11 +58,19 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
const error = insights.modelErrors[insightName];
|
|
62
|
+
if (error) {
|
|
63
|
+
return {
|
|
64
|
+
errorMessage: error.message,
|
|
65
|
+
errorStack: error.stack,
|
|
66
|
+
score: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
const insight = insights.model[insightName];
|
|
60
|
-
if (insight
|
|
71
|
+
if (!insight) {
|
|
61
72
|
return {
|
|
62
|
-
|
|
63
|
-
errorStack: insight.stack,
|
|
73
|
+
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
|
|
64
74
|
score: null,
|
|
65
75
|
};
|
|
66
76
|
}
|
|
@@ -73,7 +73,7 @@ class LayoutShifts extends Audit {
|
|
|
73
73
|
layoutShifts: new Map(),
|
|
74
74
|
};
|
|
75
75
|
for (const insightSet of traceEngineResult.insights.values()) {
|
|
76
|
-
for (const [shift, reasons] of insightSet.model.CLSCulprits
|
|
76
|
+
for (const [shift, reasons] of insightSet.model.CLSCulprits?.shifts ?? []) {
|
|
77
77
|
allRootCauses.layoutShifts.set(shift, reasons);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -51,15 +51,15 @@ class ServerResponseTime extends Audit {
|
|
|
51
51
|
const {SourceMaps, HostDPR} = artifacts;
|
|
52
52
|
const navInsights =
|
|
53
53
|
await NavigationInsights.request({trace, settings, SourceMaps, HostDPR}, context);
|
|
54
|
-
const responseTime = navInsights.model.DocumentLatency
|
|
55
|
-
const url = navInsights.model.DocumentLatency
|
|
54
|
+
const responseTime = navInsights.model.DocumentLatency?.data?.serverResponseTime;
|
|
55
|
+
const url = navInsights.model.DocumentLatency?.data?.documentRequest?.args.data.url;
|
|
56
56
|
|
|
57
57
|
if (responseTime === undefined || !url) {
|
|
58
58
|
throw new Error('no timing found for main resource');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const passed =
|
|
62
|
-
Boolean(navInsights.model.DocumentLatency
|
|
62
|
+
Boolean(navInsights.model.DocumentLatency?.data?.checklist.serverResponseIsFast.value);
|
|
63
63
|
const displayValue = str_(UIStrings.displayValue, {timeInMs: responseTime});
|
|
64
64
|
|
|
65
65
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|