diff --git a/cli/test/smokehouse/test-definitions/a11y.js b/cli/test/smokehouse/test-definitions/a11y.js index 3ea1f0c0efef..df16b5f2a9fb 100644 --- a/cli/test/smokehouse/test-definitions/a11y.js +++ b/cli/test/smokehouse/test-definitions/a11y.js @@ -698,6 +698,22 @@ const expectations = { ], }, }, + 'td-has-header': { + score: 0, + details: { + items: [ + { + node: { + 'type': 'node', + 'selector': 'body > section > table#td-has-header', + 'snippet': '', + 'explanation': 'Fix all of the following:\n Some non-empty data cells do not have table headers', + 'nodeLabel': 'FOO\tFOO\tFOO\tFOO\nfoo\tfoo\tfoo\tfoo\nfoo\tfoo\tfoo\tfoo\nfoo\tfoo\tfoo\tfoo', + }, + }, + ], + }, + }, 'td-headers-attr': { score: 0, details: { diff --git a/core/audits/accessibility/td-has-header.js b/core/audits/accessibility/td-has-header.js new file mode 100644 index 000000000000..b39ef0867556 --- /dev/null +++ b/core/audits/accessibility/td-has-header.js @@ -0,0 +1,45 @@ +/** + * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +/** + * @fileoverview Ensure that large tables have `[header]` attributes. + * See base class in axe-audit.js for audit() implementation. + */ + +import AxeAudit from './axe-audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; + +const UIStrings = { + /** Title of an accesibility audit that evaluates if all large table elements use the headers HTML attribute. This title is descriptive of the successful state and is shown to users when no user action is required. */ + title: '`
` elements in a large `` have one or more table headers.', + /** Title of an accesibility audit that evaluates if all large table elements use the headers HTML attribute. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */ + failureTitle: '`
` elements in a large `` do not have table headers.', + /** 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. */ + description: 'Screen readers have features to make navigating tables easier. Ensuring ' + + 'that `
` elements in a large table (3 or more cells in width and height) have an ' + + 'associated table header may improve the experience for screen reader users. ' + + '[Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).', +}; + +const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); + +class TDHasHeader extends AxeAudit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'td-has-header', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.failureTitle), + description: str_(UIStrings.description), + requiredArtifacts: ['Accessibility'], + }; + } +} + +export default TDHasHeader; +export {UIStrings}; diff --git a/core/config/default-config.js b/core/config/default-config.js index f4daee10028c..62f5c29c2d42 100644 --- a/core/config/default-config.js +++ b/core/config/default-config.js @@ -273,6 +273,7 @@ const defaultConfig = { 'accessibility/meta-viewport', 'accessibility/object-alt', 'accessibility/tabindex', + 'accessibility/td-has-header', 'accessibility/td-headers-attr', 'accessibility/th-has-data-cells', 'accessibility/valid-lang', @@ -537,6 +538,7 @@ const defaultConfig = { {id: 'meta-viewport', weight: 10, group: 'a11y-best-practices'}, {id: 'object-alt', weight: 3, group: 'a11y-names-labels'}, {id: 'tabindex', weight: 3, group: 'a11y-navigation'}, + {id: 'td-has-header', weight: 10, group: 'a11y-tables-lists'}, {id: 'td-headers-attr', weight: 3, group: 'a11y-tables-lists'}, {id: 'th-has-data-cells', weight: 3, group: 'a11y-tables-lists'}, {id: 'valid-lang', weight: 3, group: 'a11y-language'}, diff --git a/core/gather/gatherers/accessibility.js b/core/gather/gatherers/accessibility.js index 3bed60499ba0..d2c933f1a20a 100644 --- a/core/gather/gatherers/accessibility.js +++ b/core/gather/gatherers/accessibility.js @@ -45,7 +45,7 @@ async function runA11yChecks() { 'meta-viewport': {enabled: true}, 'duplicate-id': {enabled: false}, 'table-fake-caption': {enabled: false}, - 'td-has-header': {enabled: false}, + 'td-has-header': {enabled: true}, 'marquee': {enabled: false}, 'area-alt': {enabled: false}, 'html-xml-lang-mismatch': {enabled: false}, diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index 098dd6b5ca9f..708e98612419 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -2446,6 +2446,18 @@ "score": null, "scoreDisplayMode": "notApplicable" }, + "td-has-header": { + "id": "td-has-header", + "title": "`` elements in a large `` have one or more table headers.", + "description": "Screen readers have features to make navigating tables easier. Ensuring that `
` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, "td-headers-attr": { "id": "td-headers-attr", "title": "Cells in a `` element that use the `[headers]` attribute refer to table cells within the same table.", @@ -3905,6 +3917,11 @@ "weight": 0, "group": "a11y-navigation" }, + { + "id": "td-has-header", + "weight": 10, + "group": "a11y-tables-lists" + }, { "id": "td-headers-attr", "weight": 0, @@ -6076,342 +6093,348 @@ }, { "startTime": 213, - "name": "lh:audit:td-headers-attr", + "name": "lh:audit:td-has-header", "duration": 1, "entryType": "measure" }, { "startTime": 214, - "name": "lh:audit:th-has-data-cells", + "name": "lh:audit:td-headers-attr", "duration": 1, "entryType": "measure" }, { "startTime": 215, - "name": "lh:audit:valid-lang", + "name": "lh:audit:th-has-data-cells", "duration": 1, "entryType": "measure" }, { "startTime": 216, - "name": "lh:audit:video-caption", + "name": "lh:audit:valid-lang", "duration": 1, "entryType": "measure" }, { "startTime": 217, - "name": "lh:audit:custom-controls-labels", + "name": "lh:audit:video-caption", "duration": 1, "entryType": "measure" }, { "startTime": 218, - "name": "lh:audit:custom-controls-roles", + "name": "lh:audit:custom-controls-labels", "duration": 1, "entryType": "measure" }, { "startTime": 219, - "name": "lh:audit:focus-traps", + "name": "lh:audit:custom-controls-roles", "duration": 1, "entryType": "measure" }, { "startTime": 220, - "name": "lh:audit:focusable-controls", + "name": "lh:audit:focus-traps", "duration": 1, "entryType": "measure" }, { "startTime": 221, - "name": "lh:audit:interactive-element-affordance", + "name": "lh:audit:focusable-controls", "duration": 1, "entryType": "measure" }, { "startTime": 222, - "name": "lh:audit:logical-tab-order", + "name": "lh:audit:interactive-element-affordance", "duration": 1, "entryType": "measure" }, { "startTime": 223, - "name": "lh:audit:managed-focus", + "name": "lh:audit:logical-tab-order", "duration": 1, "entryType": "measure" }, { "startTime": 224, - "name": "lh:audit:offscreen-content-hidden", + "name": "lh:audit:managed-focus", "duration": 1, "entryType": "measure" }, { "startTime": 225, - "name": "lh:audit:use-landmarks", + "name": "lh:audit:offscreen-content-hidden", "duration": 1, "entryType": "measure" }, { "startTime": 226, - "name": "lh:audit:visual-order-follows-dom", + "name": "lh:audit:use-landmarks", "duration": 1, "entryType": "measure" }, { "startTime": 227, - "name": "lh:audit:uses-long-cache-ttl", + "name": "lh:audit:visual-order-follows-dom", "duration": 1, "entryType": "measure" }, { "startTime": 228, - "name": "lh:audit:total-byte-weight", + "name": "lh:audit:uses-long-cache-ttl", "duration": 1, "entryType": "measure" }, { "startTime": 229, - "name": "lh:audit:offscreen-images", + "name": "lh:audit:total-byte-weight", "duration": 1, "entryType": "measure" }, { "startTime": 230, - "name": "lh:audit:render-blocking-resources", + "name": "lh:audit:offscreen-images", "duration": 1, "entryType": "measure" }, { "startTime": 231, - "name": "lh:computed:UnusedCSS", + "name": "lh:audit:render-blocking-resources", "duration": 1, "entryType": "measure" }, { "startTime": 232, - "name": "lh:computed:FirstContentfulPaint", + "name": "lh:computed:UnusedCSS", "duration": 1, "entryType": "measure" }, { "startTime": 233, - "name": "lh:audit:unminified-css", + "name": "lh:computed:FirstContentfulPaint", "duration": 1, "entryType": "measure" }, { "startTime": 234, - "name": "lh:audit:unminified-javascript", + "name": "lh:audit:unminified-css", "duration": 1, "entryType": "measure" }, { "startTime": 235, - "name": "lh:audit:unused-css-rules", + "name": "lh:audit:unminified-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 236, - "name": "lh:audit:unused-javascript", + "name": "lh:audit:unused-css-rules", "duration": 1, "entryType": "measure" }, { "startTime": 237, - "name": "lh:audit:modern-image-formats", + "name": "lh:audit:unused-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 238, - "name": "lh:audit:uses-optimized-images", + "name": "lh:audit:modern-image-formats", "duration": 1, "entryType": "measure" }, { "startTime": 239, - "name": "lh:audit:uses-text-compression", + "name": "lh:audit:uses-optimized-images", "duration": 1, "entryType": "measure" }, { "startTime": 240, - "name": "lh:audit:uses-responsive-images", + "name": "lh:audit:uses-text-compression", "duration": 1, "entryType": "measure" }, { "startTime": 241, - "name": "lh:computed:ImageRecords", + "name": "lh:audit:uses-responsive-images", "duration": 1, "entryType": "measure" }, { "startTime": 242, - "name": "lh:audit:efficient-animated-content", + "name": "lh:computed:ImageRecords", "duration": 1, "entryType": "measure" }, { "startTime": 243, - "name": "lh:audit:duplicated-javascript", + "name": "lh:audit:efficient-animated-content", "duration": 1, "entryType": "measure" }, { "startTime": 244, - "name": "lh:audit:legacy-javascript", + "name": "lh:audit:duplicated-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 245, - "name": "lh:audit:doctype", + "name": "lh:audit:legacy-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 246, - "name": "lh:audit:charset", + "name": "lh:audit:doctype", "duration": 1, "entryType": "measure" }, { "startTime": 247, - "name": "lh:audit:dom-size", + "name": "lh:audit:charset", "duration": 1, "entryType": "measure" }, { "startTime": 248, - "name": "lh:audit:geolocation-on-start", + "name": "lh:audit:dom-size", "duration": 1, "entryType": "measure" }, { "startTime": 249, - "name": "lh:audit:inspector-issues", + "name": "lh:audit:geolocation-on-start", "duration": 1, "entryType": "measure" }, { "startTime": 250, - "name": "lh:audit:no-document-write", + "name": "lh:audit:inspector-issues", "duration": 1, "entryType": "measure" }, { "startTime": 251, - "name": "lh:audit:js-libraries", + "name": "lh:audit:no-document-write", "duration": 1, "entryType": "measure" }, { "startTime": 252, - "name": "lh:audit:notification-on-start", + "name": "lh:audit:js-libraries", "duration": 1, "entryType": "measure" }, { "startTime": 253, - "name": "lh:audit:paste-preventing-inputs", + "name": "lh:audit:notification-on-start", "duration": 1, "entryType": "measure" }, { "startTime": 254, - "name": "lh:audit:uses-passive-event-listeners", + "name": "lh:audit:paste-preventing-inputs", "duration": 1, "entryType": "measure" }, { "startTime": 255, - "name": "lh:audit:meta-description", + "name": "lh:audit:uses-passive-event-listeners", "duration": 1, "entryType": "measure" }, { "startTime": 256, - "name": "lh:audit:http-status-code", + "name": "lh:audit:meta-description", "duration": 1, "entryType": "measure" }, { "startTime": 257, - "name": "lh:audit:font-size", + "name": "lh:audit:http-status-code", "duration": 1, "entryType": "measure" }, { "startTime": 258, - "name": "lh:audit:link-text", + "name": "lh:audit:font-size", "duration": 1, "entryType": "measure" }, { "startTime": 259, - "name": "lh:audit:crawlable-anchors", + "name": "lh:audit:link-text", "duration": 1, "entryType": "measure" }, { "startTime": 260, - "name": "lh:audit:is-crawlable", + "name": "lh:audit:crawlable-anchors", "duration": 1, "entryType": "measure" }, { "startTime": 261, - "name": "lh:audit:robots-txt", + "name": "lh:audit:is-crawlable", "duration": 1, "entryType": "measure" }, { "startTime": 262, - "name": "lh:audit:tap-targets", + "name": "lh:audit:robots-txt", "duration": 1, "entryType": "measure" }, { "startTime": 263, - "name": "lh:audit:hreflang", + "name": "lh:audit:tap-targets", "duration": 1, "entryType": "measure" }, { "startTime": 264, - "name": "lh:audit:plugins", + "name": "lh:audit:hreflang", "duration": 1, "entryType": "measure" }, { "startTime": 265, - "name": "lh:audit:canonical", + "name": "lh:audit:plugins", "duration": 1, "entryType": "measure" }, { "startTime": 266, - "name": "lh:audit:structured-data", + "name": "lh:audit:canonical", "duration": 1, "entryType": "measure" }, { "startTime": 267, - "name": "lh:audit:bf-cache", + "name": "lh:audit:structured-data", "duration": 1, "entryType": "measure" }, { "startTime": 268, + "name": "lh:audit:bf-cache", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 269, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 269 + "total": 270 }, "i18n": { "rendererFormattedStrings": { @@ -7284,6 +7307,12 @@ "core/audits/accessibility/tabindex.js | description": [ "audits.tabindex.description" ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], "core/audits/accessibility/td-headers-attr.js | title": [ "audits[td-headers-attr].title" ], @@ -13407,6 +13436,18 @@ "score": null, "scoreDisplayMode": "notApplicable" }, + "td-has-header": { + "id": "td-has-header", + "title": "`
` elements in a large `` have one or more table headers.", + "description": "Screen readers have features to make navigating tables easier. Ensuring that `
` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, "td-headers-attr": { "id": "td-headers-attr", "title": "Cells in a `` element that use the `[headers]` attribute refer to table cells within the same table.", @@ -14187,6 +14228,11 @@ "weight": 0, "group": "a11y-navigation" }, + { + "id": "td-has-header", + "weight": 10, + "group": "a11y-tables-lists" + }, { "id": "td-headers-attr", "weight": 0, @@ -15574,174 +15620,180 @@ }, { "startTime": 72, - "name": "lh:audit:td-headers-attr", + "name": "lh:audit:td-has-header", "duration": 1, "entryType": "measure" }, { "startTime": 73, - "name": "lh:audit:th-has-data-cells", + "name": "lh:audit:td-headers-attr", "duration": 1, "entryType": "measure" }, { "startTime": 74, - "name": "lh:audit:valid-lang", + "name": "lh:audit:th-has-data-cells", "duration": 1, "entryType": "measure" }, { "startTime": 75, - "name": "lh:audit:video-caption", + "name": "lh:audit:valid-lang", "duration": 1, "entryType": "measure" }, { "startTime": 76, - "name": "lh:audit:custom-controls-labels", + "name": "lh:audit:video-caption", "duration": 1, "entryType": "measure" }, { "startTime": 77, - "name": "lh:audit:custom-controls-roles", + "name": "lh:audit:custom-controls-labels", "duration": 1, "entryType": "measure" }, { "startTime": 78, - "name": "lh:audit:focus-traps", + "name": "lh:audit:custom-controls-roles", "duration": 1, "entryType": "measure" }, { "startTime": 79, - "name": "lh:audit:focusable-controls", + "name": "lh:audit:focus-traps", "duration": 1, "entryType": "measure" }, { "startTime": 80, - "name": "lh:audit:interactive-element-affordance", + "name": "lh:audit:focusable-controls", "duration": 1, "entryType": "measure" }, { "startTime": 81, - "name": "lh:audit:logical-tab-order", + "name": "lh:audit:interactive-element-affordance", "duration": 1, "entryType": "measure" }, { "startTime": 82, - "name": "lh:audit:managed-focus", + "name": "lh:audit:logical-tab-order", "duration": 1, "entryType": "measure" }, { "startTime": 83, - "name": "lh:audit:offscreen-content-hidden", + "name": "lh:audit:managed-focus", "duration": 1, "entryType": "measure" }, { "startTime": 84, - "name": "lh:audit:use-landmarks", + "name": "lh:audit:offscreen-content-hidden", "duration": 1, "entryType": "measure" }, { "startTime": 85, - "name": "lh:audit:visual-order-follows-dom", + "name": "lh:audit:use-landmarks", "duration": 1, "entryType": "measure" }, { "startTime": 86, - "name": "lh:audit:uses-responsive-images-snapshot", + "name": "lh:audit:visual-order-follows-dom", "duration": 1, "entryType": "measure" }, { "startTime": 87, - "name": "lh:audit:doctype", + "name": "lh:audit:uses-responsive-images-snapshot", "duration": 1, "entryType": "measure" }, { "startTime": 88, - "name": "lh:audit:dom-size", + "name": "lh:audit:doctype", "duration": 1, "entryType": "measure" }, { "startTime": 89, - "name": "lh:audit:js-libraries", + "name": "lh:audit:dom-size", "duration": 1, "entryType": "measure" }, { "startTime": 90, - "name": "lh:audit:paste-preventing-inputs", + "name": "lh:audit:js-libraries", "duration": 1, "entryType": "measure" }, { "startTime": 91, - "name": "lh:audit:meta-description", + "name": "lh:audit:paste-preventing-inputs", "duration": 1, "entryType": "measure" }, { "startTime": 92, - "name": "lh:audit:font-size", + "name": "lh:audit:meta-description", "duration": 1, "entryType": "measure" }, { "startTime": 93, - "name": "lh:audit:link-text", + "name": "lh:audit:font-size", "duration": 1, "entryType": "measure" }, { "startTime": 94, - "name": "lh:audit:crawlable-anchors", + "name": "lh:audit:link-text", "duration": 1, "entryType": "measure" }, { "startTime": 95, - "name": "lh:audit:robots-txt", + "name": "lh:audit:crawlable-anchors", "duration": 1, "entryType": "measure" }, { "startTime": 96, - "name": "lh:audit:tap-targets", + "name": "lh:audit:robots-txt", "duration": 1, "entryType": "measure" }, { "startTime": 97, - "name": "lh:audit:plugins", + "name": "lh:audit:tap-targets", "duration": 1, "entryType": "measure" }, { "startTime": 98, - "name": "lh:audit:structured-data", + "name": "lh:audit:plugins", "duration": 1, "entryType": "measure" }, { "startTime": 99, + "name": "lh:audit:structured-data", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 100, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 100 + "total": 101 }, "i18n": { "rendererFormattedStrings": { @@ -16085,6 +16137,12 @@ "core/audits/accessibility/tabindex.js | description": [ "audits.tabindex.description" ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], "core/audits/accessibility/td-headers-attr.js | title": [ "audits[td-headers-attr].title" ], @@ -18793,6 +18851,18 @@ "score": null, "scoreDisplayMode": "notApplicable" }, + "td-has-header": { + "id": "td-has-header", + "title": "`
` elements in a large `` have one or more table headers.", + "description": "Screen readers have features to make navigating tables easier. Ensuring that `
` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, "td-headers-attr": { "id": "td-headers-attr", "title": "Cells in a `` element that use the `[headers]` attribute refer to table cells within the same table.", @@ -20379,6 +20449,11 @@ "weight": 0, "group": "a11y-navigation" }, + { + "id": "td-has-header", + "weight": 10, + "group": "a11y-tables-lists" + }, { "id": "td-headers-attr", "weight": 0, @@ -20441,7 +20516,7 @@ } ], "id": "accessibility", - "score": 0.92 + "score": 0.93 }, "best-practices": { "title": "Best Practices", @@ -22524,342 +22599,348 @@ }, { "startTime": 210, - "name": "lh:audit:td-headers-attr", + "name": "lh:audit:td-has-header", "duration": 1, "entryType": "measure" }, { "startTime": 211, - "name": "lh:audit:th-has-data-cells", + "name": "lh:audit:td-headers-attr", "duration": 1, "entryType": "measure" }, { "startTime": 212, - "name": "lh:audit:valid-lang", + "name": "lh:audit:th-has-data-cells", "duration": 1, "entryType": "measure" }, { "startTime": 213, - "name": "lh:audit:video-caption", + "name": "lh:audit:valid-lang", "duration": 1, "entryType": "measure" }, { "startTime": 214, - "name": "lh:audit:custom-controls-labels", + "name": "lh:audit:video-caption", "duration": 1, "entryType": "measure" }, { "startTime": 215, - "name": "lh:audit:custom-controls-roles", + "name": "lh:audit:custom-controls-labels", "duration": 1, "entryType": "measure" }, { "startTime": 216, - "name": "lh:audit:focus-traps", + "name": "lh:audit:custom-controls-roles", "duration": 1, "entryType": "measure" }, { "startTime": 217, - "name": "lh:audit:focusable-controls", + "name": "lh:audit:focus-traps", "duration": 1, "entryType": "measure" }, { "startTime": 218, - "name": "lh:audit:interactive-element-affordance", + "name": "lh:audit:focusable-controls", "duration": 1, "entryType": "measure" }, { "startTime": 219, - "name": "lh:audit:logical-tab-order", + "name": "lh:audit:interactive-element-affordance", "duration": 1, "entryType": "measure" }, { "startTime": 220, - "name": "lh:audit:managed-focus", + "name": "lh:audit:logical-tab-order", "duration": 1, "entryType": "measure" }, { "startTime": 221, - "name": "lh:audit:offscreen-content-hidden", + "name": "lh:audit:managed-focus", "duration": 1, "entryType": "measure" }, { "startTime": 222, - "name": "lh:audit:use-landmarks", + "name": "lh:audit:offscreen-content-hidden", "duration": 1, "entryType": "measure" }, { "startTime": 223, - "name": "lh:audit:visual-order-follows-dom", + "name": "lh:audit:use-landmarks", "duration": 1, "entryType": "measure" }, { "startTime": 224, - "name": "lh:audit:uses-long-cache-ttl", + "name": "lh:audit:visual-order-follows-dom", "duration": 1, "entryType": "measure" }, { "startTime": 225, - "name": "lh:audit:total-byte-weight", + "name": "lh:audit:uses-long-cache-ttl", "duration": 1, "entryType": "measure" }, { "startTime": 226, - "name": "lh:audit:offscreen-images", + "name": "lh:audit:total-byte-weight", "duration": 1, "entryType": "measure" }, { "startTime": 227, - "name": "lh:audit:render-blocking-resources", + "name": "lh:audit:offscreen-images", "duration": 1, "entryType": "measure" }, { "startTime": 228, - "name": "lh:computed:UnusedCSS", + "name": "lh:audit:render-blocking-resources", "duration": 1, "entryType": "measure" }, { "startTime": 229, - "name": "lh:computed:FirstContentfulPaint", + "name": "lh:computed:UnusedCSS", "duration": 1, "entryType": "measure" }, { "startTime": 230, - "name": "lh:audit:unminified-css", + "name": "lh:computed:FirstContentfulPaint", "duration": 1, "entryType": "measure" }, { "startTime": 231, - "name": "lh:audit:unminified-javascript", + "name": "lh:audit:unminified-css", "duration": 1, "entryType": "measure" }, { "startTime": 232, - "name": "lh:audit:unused-css-rules", + "name": "lh:audit:unminified-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 233, - "name": "lh:audit:unused-javascript", + "name": "lh:audit:unused-css-rules", "duration": 1, "entryType": "measure" }, { "startTime": 234, - "name": "lh:audit:modern-image-formats", + "name": "lh:audit:unused-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 235, - "name": "lh:audit:uses-optimized-images", + "name": "lh:audit:modern-image-formats", "duration": 1, "entryType": "measure" }, { "startTime": 236, - "name": "lh:audit:uses-text-compression", + "name": "lh:audit:uses-optimized-images", "duration": 1, "entryType": "measure" }, { "startTime": 237, - "name": "lh:audit:uses-responsive-images", + "name": "lh:audit:uses-text-compression", "duration": 1, "entryType": "measure" }, { "startTime": 238, - "name": "lh:computed:ImageRecords", + "name": "lh:audit:uses-responsive-images", "duration": 1, "entryType": "measure" }, { "startTime": 239, - "name": "lh:audit:efficient-animated-content", + "name": "lh:computed:ImageRecords", "duration": 1, "entryType": "measure" }, { "startTime": 240, - "name": "lh:audit:duplicated-javascript", + "name": "lh:audit:efficient-animated-content", "duration": 1, "entryType": "measure" }, { "startTime": 241, - "name": "lh:audit:legacy-javascript", + "name": "lh:audit:duplicated-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 242, - "name": "lh:audit:doctype", + "name": "lh:audit:legacy-javascript", "duration": 1, "entryType": "measure" }, { "startTime": 243, - "name": "lh:audit:charset", + "name": "lh:audit:doctype", "duration": 1, "entryType": "measure" }, { "startTime": 244, - "name": "lh:audit:dom-size", + "name": "lh:audit:charset", "duration": 1, "entryType": "measure" }, { "startTime": 245, - "name": "lh:audit:geolocation-on-start", + "name": "lh:audit:dom-size", "duration": 1, "entryType": "measure" }, { "startTime": 246, - "name": "lh:audit:inspector-issues", + "name": "lh:audit:geolocation-on-start", "duration": 1, "entryType": "measure" }, { "startTime": 247, - "name": "lh:audit:no-document-write", + "name": "lh:audit:inspector-issues", "duration": 1, "entryType": "measure" }, { "startTime": 248, - "name": "lh:audit:js-libraries", + "name": "lh:audit:no-document-write", "duration": 1, "entryType": "measure" }, { "startTime": 249, - "name": "lh:audit:notification-on-start", + "name": "lh:audit:js-libraries", "duration": 1, "entryType": "measure" }, { "startTime": 250, - "name": "lh:audit:paste-preventing-inputs", + "name": "lh:audit:notification-on-start", "duration": 1, "entryType": "measure" }, { "startTime": 251, - "name": "lh:audit:uses-passive-event-listeners", + "name": "lh:audit:paste-preventing-inputs", "duration": 1, "entryType": "measure" }, { "startTime": 252, - "name": "lh:audit:meta-description", + "name": "lh:audit:uses-passive-event-listeners", "duration": 1, "entryType": "measure" }, { "startTime": 253, - "name": "lh:audit:http-status-code", + "name": "lh:audit:meta-description", "duration": 1, "entryType": "measure" }, { "startTime": 254, - "name": "lh:audit:font-size", + "name": "lh:audit:http-status-code", "duration": 1, "entryType": "measure" }, { "startTime": 255, - "name": "lh:audit:link-text", + "name": "lh:audit:font-size", "duration": 1, "entryType": "measure" }, { "startTime": 256, - "name": "lh:audit:crawlable-anchors", + "name": "lh:audit:link-text", "duration": 1, "entryType": "measure" }, { "startTime": 257, - "name": "lh:audit:is-crawlable", + "name": "lh:audit:crawlable-anchors", "duration": 1, "entryType": "measure" }, { "startTime": 258, - "name": "lh:audit:robots-txt", + "name": "lh:audit:is-crawlable", "duration": 1, "entryType": "measure" }, { "startTime": 259, - "name": "lh:audit:tap-targets", + "name": "lh:audit:robots-txt", "duration": 1, "entryType": "measure" }, { "startTime": 260, - "name": "lh:audit:hreflang", + "name": "lh:audit:tap-targets", "duration": 1, "entryType": "measure" }, { "startTime": 261, - "name": "lh:audit:plugins", + "name": "lh:audit:hreflang", "duration": 1, "entryType": "measure" }, { "startTime": 262, - "name": "lh:audit:canonical", + "name": "lh:audit:plugins", "duration": 1, "entryType": "measure" }, { "startTime": 263, - "name": "lh:audit:structured-data", + "name": "lh:audit:canonical", "duration": 1, "entryType": "measure" }, { "startTime": 264, - "name": "lh:audit:bf-cache", + "name": "lh:audit:structured-data", "duration": 1, "entryType": "measure" }, { "startTime": 265, + "name": "lh:audit:bf-cache", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 266, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 266 + "total": 267 }, "i18n": { "rendererFormattedStrings": { @@ -23727,6 +23808,12 @@ "core/audits/accessibility/tabindex.js | description": [ "audits.tabindex.description" ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], "core/audits/accessibility/td-headers-attr.js | title": [ "audits[td-headers-attr].title" ], diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 8bcae53c9ede..5b4edf941125 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -3750,6 +3750,18 @@ "score": null, "scoreDisplayMode": "notApplicable" }, + "td-has-header": { + "id": "td-has-header", + "title": "`
` elements in a large `` have one or more table headers.", + "description": "Screen readers have features to make navigating tables easier. Ensuring that `
` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, "td-headers-attr": { "id": "td-headers-attr", "title": "Cells in a `` element that use the `[headers]` attribute refer to table cells within the same table.", @@ -6011,6 +6023,11 @@ "weight": 0, "group": "a11y-navigation" }, + { + "id": "td-has-header", + "weight": 10, + "group": "a11y-tables-lists" + }, { "id": "td-headers-attr", "weight": 0, @@ -6073,7 +6090,7 @@ } ], "id": "accessibility", - "score": 0.77 + "score": 0.79 }, "best-practices": { "title": "Best Practices", @@ -8348,6 +8365,12 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:td-has-header", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:audit:td-headers-attr", @@ -9731,6 +9754,12 @@ "core/audits/accessibility/tabindex.js | description": [ "audits.tabindex.description" ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], "core/audits/accessibility/td-headers-attr.js | title": [ "audits[td-headers-attr].title" ], diff --git a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap index 67f596a9ddf3..d5d3c6497b69 100644 --- a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap +++ b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap @@ -124,6 +124,7 @@ Array [ "structured-data", "tabindex", "tap-targets", + "td-has-header", "td-headers-attr", "th-has-data-cells", "themed-omnibox", @@ -279,6 +280,7 @@ Array [ "structured-data", "tabindex", "tap-targets", + "td-has-header", "td-headers-attr", "th-has-data-cells", "themed-omnibox", @@ -374,6 +376,7 @@ Array [ "structured-data", "tabindex", "tap-targets", + "td-has-header", "td-headers-attr", "th-has-data-cells", "unsized-images", diff --git a/report/test/generator/report-generator-test.js b/report/test/generator/report-generator-test.js index a4b8957cbfbf..15ea5deb9068 100644 --- a/report/test/generator/report-generator-test.js +++ b/report/test/generator/report-generator-test.js @@ -107,7 +107,7 @@ describe('ReportGenerator', () => { category,score \\"performance\\",\\"0.28\\" -\\"accessibility\\",\\"0.77\\" +\\"accessibility\\",\\"0.79\\" \\"best-practices\\",\\"0.33\\" \\"seo\\",\\"0.67\\" \\"pwa\\",\\"0.33\\" diff --git a/report/test/renderer/category-renderer-test.js b/report/test/renderer/category-renderer-test.js index 76f5e3ae40fe..bcca574597cb 100644 --- a/report/test/renderer/category-renderer-test.js +++ b/report/test/renderer/category-renderer-test.js @@ -301,7 +301,7 @@ describe('CategoryRenderer', () => { ); const gauge = categoryDOM.querySelector('.lh-fraction__content'); - assert.equal(gauge.textContent.trim(), '12/17', 'fraction is included'); + assert.equal(gauge.textContent.trim(), '13/18', 'fraction is included'); const score = categoryDOM.querySelector('.lh-category-header'); const title = score.querySelector('.lh-fraction__label'); diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index f1be66d8d04f..37a1151844b2 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -362,6 +362,15 @@ "core/audits/accessibility/tabindex.js | title": { "message": "No element has a `[tabindex]` value greater than 0" }, + "core/audits/accessibility/td-has-header.js | description": { + "message": "Screen readers have features to make navigating tables easier. Ensuring that `
` elements in a large table (3 or more cells in width and height) have an associated table header may improve the experience for screen reader users. [Learn more about table headers](https://dequeuniversity.com/rules/axe/4.7/td-has-header)." + }, + "core/audits/accessibility/td-has-header.js | failureTitle": { + "message": "`` elements in a large `` do not have table headers." + }, + "core/audits/accessibility/td-has-header.js | title": { + "message": "`
` elements in a large `` have one or more table headers." + }, "core/audits/accessibility/td-headers-attr.js | description": { "message": "Screen readers have features to make navigating tables easier. Ensuring `
` cells using the `[headers]` attribute only refer to other cells in the same table may improve the experience for screen reader users. [Learn more about the `headers` attribute](https://dequeuniversity.com/rules/axe/4.7/td-headers-attr)." }, diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index bda328997996..2d4ac8ca566b 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -362,6 +362,15 @@ "core/audits/accessibility/tabindex.js | title": { "message": "N̂ó êĺêḿêńt̂ h́âś â `[tabindex]` v́âĺûé ĝŕêát̂ér̂ t́ĥán̂ 0" }, + "core/audits/accessibility/td-has-header.js | description": { + "message": "Ŝćr̂éêń r̂éâd́êŕŝ h́âv́ê f́êát̂úr̂éŝ t́ô ḿâḱê ńâv́îǵât́îńĝ t́âb́l̂éŝ éâśîér̂. Én̂śûŕîńĝ t́ĥát̂ `` él̂ém̂én̂t́ŝ ín̂ á l̂ár̂ǵê t́âb́l̂é (3 ôŕ m̂ór̂é ĉél̂ĺŝ ín̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂) h́âv́ê án̂ áŝśôćîát̂éd̂ t́âb́l̂é ĥéâd́êŕ m̂áŷ ím̂ṕr̂óv̂é t̂h́ê éx̂ṕêŕîén̂ćê f́ôŕ ŝćr̂éêń r̂éâd́êŕ ûśêŕŝ. [Ĺêár̂ń m̂ór̂é âb́ôút̂ t́âb́l̂é ĥéâd́êŕŝ](https://dequeuniversity.com/rules/axe/4.7/td-has-header)." + }, + "core/audits/accessibility/td-has-header.js | failureTitle": { + "message": "`` êĺêḿêńt̂ś îń â ĺâŕĝé `` d̂ó n̂ót̂ h́âv́ê t́âb́l̂é ĥéâd́êŕŝ." + }, + "core/audits/accessibility/td-has-header.js | title": { + "message": "`
` êĺêḿêńt̂ś îń â ĺâŕĝé `` ĥáv̂é ôńê ór̂ ḿôŕê t́âb́l̂é ĥéâd́êŕŝ." + }, "core/audits/accessibility/td-headers-attr.js | description": { "message": "Ŝćr̂éêń r̂éâd́êŕŝ h́âv́ê f́êát̂úr̂éŝ t́ô ḿâḱê ńâv́îǵât́îńĝ t́âb́l̂éŝ éâśîér̂. Én̂śûŕîńĝ `
` ćêĺl̂ś ûśîńĝ t́ĥé `[headers]` ât́t̂ŕîb́ût́ê ón̂ĺŷ ŕêf́êŕ t̂ó ôt́ĥér̂ ćêĺl̂ś îń t̂h́ê śâḿê t́âb́l̂é m̂áŷ ím̂ṕr̂óv̂é t̂h́ê éx̂ṕêŕîén̂ćê f́ôŕ ŝćr̂éêń r̂éâd́êŕ ûśêŕŝ. [Ĺêár̂ń m̂ór̂é âb́ôút̂ t́ĥé `headers` ât́t̂ŕîb́ût́ê](https://dequeuniversity.com/rules/axe/4.7/td-headers-attr)." }, diff --git a/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts b/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts index 4afc00788b52..c184515e1c77 100644 --- a/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts +++ b/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts @@ -133,7 +133,7 @@ describe.skipOnParallel('Navigation', async function() { }); const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr); - assert.strictEqual(auditResults.length, 173); + assert.strictEqual(auditResults.length, 174); assert.deepStrictEqual(erroredAudits, []); assert.deepStrictEqual(failedAudits.map(audit => audit.id), [ 'service-worker', @@ -222,7 +222,7 @@ describe.skipOnParallel('Navigation', async function() { ]; const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr, flakyAudits); - assert.strictEqual(auditResults.length, 150); + assert.strictEqual(auditResults.length, 151); assert.deepStrictEqual(erroredAudits, []); assert.deepStrictEqual(failedAudits.map(audit => audit.id), [ 'service-worker', diff --git a/third-party/devtools-tests/e2e/lighthouse/snapshot_test.ts b/third-party/devtools-tests/e2e/lighthouse/snapshot_test.ts index 8dff1966c593..6615955cfe0c 100644 --- a/third-party/devtools-tests/e2e/lighthouse/snapshot_test.ts +++ b/third-party/devtools-tests/e2e/lighthouse/snapshot_test.ts @@ -79,7 +79,7 @@ describe.skipOnParallel('Snapshot', async function() { }); const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr); - assert.strictEqual(auditResults.length, 71); + assert.strictEqual(auditResults.length, 72); assert.deepStrictEqual(erroredAudits, []); assert.deepStrictEqual(failedAudits.map(audit => audit.id), [ 'document-title',