diff --git a/lighthouse-cli/test/smokehouse/a11y/expectations.js b/lighthouse-cli/test/smokehouse/a11y/expectations.js index bc3e29fa3fd5..9f29e47c349b 100644 --- a/lighthouse-cli/test/smokehouse/a11y/expectations.js +++ b/lighthouse-cli/test/smokehouse/a11y/expectations.js @@ -5,6 +5,8 @@ */ 'use strict'; +/* eslint-disable max-len */ + /** * Expected Lighthouse audit values for byte efficiency tests */ @@ -17,225 +19,442 @@ module.exports = [ 'aria-allowed-attr': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#aria-allowed-attr', + 'snippet': '', + 'explanation': 'Fix any of the following:\n ARIA attribute is not allowed: aria-checked="true"', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'aria-required-children': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#aria-required-children', + 'snippet': '
\n
\n
', + 'explanation': 'Fix any of the following:\n Required ARIA child role not present: radio', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'aria-required-parent': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#aria-required-parent', + 'snippet': '
\n
', + 'explanation': 'Fix any of the following:\n Required ARIA parent role not present: listbox', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'aria-roles': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'div[role="foo"]', + 'snippet': '
', + 'explanation': 'Fix all of the following:\n Role must be one of the valid ARIA roles', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'aria-valid-attr-value': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#aria-valid-attr-value', + 'snippet': '', + 'explanation': 'Fix all of the following:\n Invalid ARIA attribute value: aria-checked="0"', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'aria-valid-attr': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#aria-valid-attr', + 'snippet': '', + 'explanation': 'Fix any of the following:\n Invalid ARIA attribute name: aria-chked', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'button-name': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#button-name', + 'snippet': '', + 'explanation': 'Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element has a value attribute and the value attribute is empty\n Element has no value attribute or the value attribute is empty\n Element does not have inner text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element\'s default semantics were not overridden with role="presentation"\n Element\'s default semantics were not overridden with role="none"\n Element has no title attribute or the title attribute is empty', + 'nodeLabel': 'button', + }, + }, + ], }, }, 'bypass': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'html', + 'snippet': '', + 'explanation': 'Fix any of the following:\n No valid skip link found\n Page does not have a header\n Page does not have a landmark region', + 'nodeLabel': 'html', + }, + }, + ], }, }, 'color-contrast': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#color-contrast', + 'snippet': '
\n Hello\n
', + 'explanation': 'Fix any of the following:\n Element has insufficient color contrast of 2.59 (foreground color: #ffc0cb, background color: #ff0000, font size: 28.5pt, font weight: normal). Expected contrast ratio of 3:1', + 'nodeLabel': 'Hello', + }, + }, + ], }, }, 'definition-list': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#definition-list', + 'snippet': '
\n

\n
', + 'explanation': 'Fix all of the following:\n List element has direct children that are not allowed inside
or
elements', + 'nodeLabel': 'dl', + }, + }, + ], }, }, 'dlitem': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'dd', + 'snippet': '
', + 'explanation': 'Fix any of the following:\n Description list item does not have a
parent element', + 'nodeLabel': 'dd', + }, + }, + ], }, }, 'document-title': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'html', + 'snippet': '', + 'explanation': 'Fix any of the following:\n Document does not have a non-empty element', + 'nodeLabel': 'html', + }, + }, + ], + }, }, 'duplicate-id': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'section:nth-child(28) > div:nth-child(1)', + 'snippet': '<div id="duplicate-id"></div>', + 'explanation': 'Fix any of the following:\n Document has multiple static elements with the same id attribute', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'frame-title': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#frame-title', + 'snippet': '<iframe id="frame-title"></iframe>', + 'explanation': 'Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element\'s default semantics were not overridden with role="presentation"\n Element\'s default semantics were not overridden with role="none"', + 'nodeLabel': 'iframe', + }, + }, + ], }, }, 'html-has-lang': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'html', + 'snippet': '<html>', + 'explanation': 'Fix any of the following:\n The <html> element does not have a lang attribute', + 'nodeLabel': 'html', + }, + }, + ], }, }, 'image-alt': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#image-alt', + 'snippet': '<img id="image-alt" src="./bogus.jpg">', + 'explanation': 'Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element\'s default semantics were not overridden with role="presentation"\n Element\'s default semantics were not overridden with role="none"', + 'nodeLabel': 'img', + }, + }, + ], }, }, 'input-image-alt': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#input-image-alt', + 'snippet': '<input type="image" id="input-image-alt">', + 'explanation': 'Fix any of the following:\n Element has no alt attribute or the alt attribute is empty\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty', + 'nodeLabel': 'input', + }, + }, + ], }, }, 'label': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#label', + 'snippet': '<input id="label" type="text">', + 'explanation': 'Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty', + 'nodeLabel': 'input', + }, + }, + ], }, }, 'layout-table': { score: 1, details: { - items: { - length: 0, - }, + 'type': 'table', + 'headings': [], + 'items': [], }, }, 'link-name': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#link-name', + 'snippet': '<a id="link-name" href="google.com"></a>', + 'explanation': 'Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element\'s default semantics were not overridden with role="presentation"\n Element\'s default semantics were not overridden with role="none"', + 'nodeLabel': 'a', + }, + }, + ], }, }, 'list': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#list', + 'snippet': '<ul id="list">\n <p></p>\n </ul>', + 'explanation': 'Fix all of the following:\n List element has direct children that are not allowed inside <li> elements', + 'nodeLabel': 'ul', + }, + }, + ], }, }, 'listitem': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#listitem', + 'snippet': '<li id="listitem"></li>', + 'explanation': 'Fix any of the following:\n List item does not have a <ul>, <ol> or role="list" parent element', + 'nodeLabel': 'li', + }, + }, + ], }, }, 'meta-viewport': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': 'meta[name="viewport"]', + 'snippet': '<meta name="viewport" content="user-scalable=no, maximum-scale=1.0">', + 'explanation': 'Fix any of the following:\n user-scalable=no on <meta> tag disables zooming on mobile devices', + 'nodeLabel': 'meta', + }, + }, + ], }, }, 'object-alt': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#object-alt', + 'snippet': '<object id="object-alt"></object>', + 'explanation': 'Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element\'s default semantics were not overridden with role="presentation"\n Element\'s default semantics were not overridden with role="none"', + 'nodeLabel': 'object', + }, + }, + ], }, }, 'tabindex': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#tabindex', + 'snippet': '<div id="tabindex" tabindex="10">\n </div>', + 'explanation': 'Fix any of the following:\n Element has a tabindex greater than 0', + 'nodeLabel': 'div', + }, + }, + ], }, }, 'td-headers-attr': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#td-headers-attr', + 'snippet': '<table id="td-headers-attr">\n\t\t\t <tbody><tr><th>FOO</th></tr>\n\t\t\t <tr><td headers="bogus-td-headers-attr">foo</td></tr>\n\t\t\t</tbody></table>', + 'explanation': 'Fix all of the following:\n The headers attribute is not exclusively used to refer to other cells in the table', + 'nodeLabel': 'FOO\nfoo', + }, + }, + ], }, }, 'valid-lang': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#valid-lang', + 'snippet': '<p id="valid-lang" lang="foo">foo</p>', + 'explanation': 'Fix all of the following:\n Value of lang attribute not included in the list of valid languages', + 'nodeLabel': 'foo', + }, + }, + ], }, }, 'accesskeys': { score: 0, details: { - items: { - length: 1, - }, + items: [ + { + node: { + 'type': 'node', + 'selector': '#accesskeys1', + 'snippet': '<button id="accesskeys1" accesskey="s">Foo</button>', + 'explanation': 'Fix all of the following:\n Document has multiple elements with the same accesskey', + 'nodeLabel': 'Foo', + }, + }, + ], }, }, }, diff --git a/lighthouse-cli/test/smokehouse/seo/expectations.js b/lighthouse-cli/test/smokehouse/seo/expectations.js index 4057997f1bb5..8b8e5f71ac34 100644 --- a/lighthouse-cli/test/smokehouse/seo/expectations.js +++ b/lighthouse-cli/test/smokehouse/seo/expectations.js @@ -205,6 +205,7 @@ module.exports = [ '\n too small target\n </a>', 'path': '2,HTML,1,BODY,3,DIV,21,DIV,0,A', 'selector': 'body > div > div > a', + 'nodeLabel': 'too small target', }, 'overlappingTarget': { 'type': 'node', @@ -213,6 +214,7 @@ module.exports = [ '\n big enough target\n </a>', 'path': '2,HTML,1,BODY,3,DIV,21,DIV,1,A', 'selector': 'body > div > div > a', + 'nodeLabel': 'big enough target', }, 'size': '100x30', 'width': 100, diff --git a/lighthouse-core/audits/accessibility/axe-audit.js b/lighthouse-core/audits/accessibility/axe-audit.js index 56d418e849b4..b2f9c210ad1c 100644 --- a/lighthouse-core/audits/accessibility/axe-audit.js +++ b/lighthouse-core/audits/accessibility/axe-audit.js @@ -54,6 +54,7 @@ class AxeAudit extends Audit { path: node.path, snippet: node.html || node.snippet, explanation: node.failureSummary, + nodeLabel: node.nodeLabel, }), })); } diff --git a/lighthouse-core/audits/seo/tap-targets.js b/lighthouse-core/audits/seo/tap-targets.js index 25afeaaf92a2..29009a5683bb 100644 --- a/lighthouse-core/audits/seo/tap-targets.js +++ b/lighthouse-core/audits/seo/tap-targets.js @@ -251,6 +251,7 @@ function targetToTableNode(target) { snippet: target.snippet, path: target.path, selector: target.selector, + nodeLabel: target.nodeLabel, }; } diff --git a/lighthouse-core/gather/gatherers/accessibility.js b/lighthouse-core/gather/gatherers/accessibility.js index 57cb71cfb4f0..9d0e9165f9c6 100644 --- a/lighthouse-core/gather/gatherers/accessibility.js +++ b/lighthouse-core/gather/gatherers/accessibility.js @@ -5,7 +5,7 @@ */ 'use strict'; -/* global window, document, getOuterHTMLSnippet, getNodePath */ +/* global window, document, getOuterHTMLSnippet, getNodePath, getNodeLabel */ const Gatherer = require('./gatherer'); const fs = require('fs'); @@ -57,6 +57,8 @@ function runA11yChecks() { node.path = getNodePath(node.element); // @ts-ignore - getOuterHTMLSnippet put into scope via stringification node.snippet = getOuterHTMLSnippet(node.element); + // @ts-ignore - getNodeLabel put into scope via stringification + node.nodeLabel = getNodeLabel(node.element); // avoid circular JSON concerns node.element = node.any = node.all = node.none = undefined; })); @@ -77,6 +79,7 @@ class Accessibility extends Gatherer { const expression = `(function () { ${pageFunctions.getOuterHTMLSnippetString}; ${pageFunctions.getNodePathString}; + ${pageFunctions.getNodeLabelString}; ${axeLibSource}; return (${runA11yChecks.toString()}()); })()`; diff --git a/lighthouse-core/gather/gatherers/seo/tap-targets.js b/lighthouse-core/gather/gatherers/seo/tap-targets.js index 6314c60f7e73..6e559178243f 100644 --- a/lighthouse-core/gather/gatherers/seo/tap-targets.js +++ b/lighthouse-core/gather/gatherers/seo/tap-targets.js @@ -5,7 +5,7 @@ */ 'use strict'; -/* global getComputedStyle, getElementsInDocument, Node, getNodePath, getNodeSelector */ +/* global getComputedStyle, getElementsInDocument, Node, getNodePath, getNodeSelector, getNodeLabel */ const Gatherer = require('../gatherer'); const pageFunctions = require('../../../lib/page-functions.js'); @@ -239,7 +239,7 @@ function elementIsPositionFixedStickyOrAbsolute(element) { /** * @param {string} str * @param {number} maxLength - * @returns {string} + * @return {string} */ /* istanbul ignore next */ function truncate(str, maxLength) { @@ -294,6 +294,8 @@ function gatherTapTargets() { path: getNodePath(tapTargetElement), // @ts-ignore - getNodeSelector put into scope via stringification selector: getNodeSelector(tapTargetElement), + // @ts-ignore - getNodeLabel put into scope via stringification + nodeLabel: getNodeLabel(tapTargetElement), href: /** @type {HTMLAnchorElement} */(tapTargetElement)['href'] || '', }); }); @@ -323,6 +325,7 @@ class TapTargets extends Gatherer { ${rectContainsString}; ${pageFunctions.getNodePathString}; ${pageFunctions.getNodeSelectorString}; + ${pageFunctions.getNodeLabelString}; ${gatherTapTargets.toString()}; return gatherTapTargets(); diff --git a/lighthouse-core/lib/page-functions.js b/lighthouse-core/lib/page-functions.js index 9e9b79d69304..4a09367b8468 100644 --- a/lighthouse-core/lib/page-functions.js +++ b/lighthouse-core/lib/page-functions.js @@ -187,7 +187,7 @@ function getNodePath(node) { /** * @param {Element} node - * @returns {string} + * @return {string} */ /* istanbul ignore next */ function getNodeSelector(node) { @@ -218,6 +218,47 @@ function getNodeSelector(node) { return parts.join(' > '); } +/** + * Generate a human-readable label for the given element, based on end-user facing + * strings like the innerText or alt attribute. + * Falls back to the tagName if no useful label is found. + * @param {HTMLElement} node + * @return {string|null} + */ +/* istanbul ignore next */ +function getNodeLabel(node) { + // Inline so that audits that import getNodeLabel don't + // also need to import truncate + /** + * @param {string} str + * @param {number} maxLength + * @return {string} + */ + function truncate(str, maxLength) { + if (str.length <= maxLength) { + return str; + } + return str.slice(0, maxLength - 1) + '…'; + } + + const tagName = node.tagName.toLowerCase(); + // html and body content is too broad to be useful, since they contain all page content + if (tagName !== 'html' && tagName !== 'body') { + const nodeLabel = node.innerText || node.getAttribute('alt') || node.getAttribute('aria-label'); + if (nodeLabel) { + return truncate(nodeLabel, 80); + } else { + // If no useful label was found then try to get one from a child. + // E.g. if an a tag contains an image but no text we want the image alt/aria-label attribute. + const nodeToUseForLabel = node.querySelector('[alt], [aria-label]'); + if (nodeToUseForLabel) { + return getNodeLabel(/** @type {HTMLElement} */ (nodeToUseForLabel)); + } + } + } + return tagName; +} + module.exports = { wrapRuntimeEvalErrorInBrowserString: wrapRuntimeEvalErrorInBrowser.toString(), registerPerformanceObserverInPageString: registerPerformanceObserverInPage.toString(), @@ -230,4 +271,6 @@ module.exports = { getNodePathString: getNodePath.toString(), getNodeSelectorString: getNodeSelector.toString(), getNodeSelector: getNodeSelector, + getNodeLabel: getNodeLabel, + getNodeLabelString: getNodeLabel.toString(), }; diff --git a/lighthouse-core/report/html/renderer/details-renderer.js b/lighthouse-core/report/html/renderer/details-renderer.js index 67ac6b38998c..d7cb6abe0e34 100644 --- a/lighthouse-core/report/html/renderer/details-renderer.js +++ b/lighthouse-core/report/html/renderer/details-renderer.js @@ -352,8 +352,16 @@ class DetailsRenderer { */ renderNode(item) { const element = this._dom.createElement('span', 'lh-node'); + if (item.nodeLabel) { + const nodeLabelEl = this._dom.createElement('div'); + nodeLabelEl.textContent = item.nodeLabel; + element.appendChild(nodeLabelEl); + } if (item.snippet) { - element.textContent = item.snippet; + const snippetEl = this._dom.createElement('div'); + snippetEl.classList.add('lh-node__snippet'); + snippetEl.textContent = item.snippet; + element.appendChild(snippetEl); } if (item.selector) { element.title = item.selector; @@ -361,6 +369,7 @@ class DetailsRenderer { if (item.path) element.setAttribute('data-path', item.path); if (item.selector) element.setAttribute('data-selector', item.selector); if (item.snippet) element.setAttribute('data-snippet', item.snippet); + return element; } diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css index 0ba4ca75d321..3dab01f7d2d9 100644 --- a/lighthouse-core/report/html/report-styles.css +++ b/lighthouse-core/report/html/report-styles.css @@ -80,7 +80,8 @@ --chevron-size: 12px; --inner-audit-left-padding: calc(var(--score-shape-size) + var(--score-shape-margin-left) + var(--score-shape-margin-right)); - /* Palette. */ + /* Palette using Material Design Colors + * https://www.materialui.co/colors */ --color-black-100: #F5F5F5; --color-black-200: #E0E0E0; --color-black-400: #BDBDBD; @@ -100,6 +101,8 @@ --color-white: #FFFFFF; --color-blue-200: #90CAF9; --color-blue-900: #0D47A1; + --color-cyan-500: #00BCD4; + --color-teal-600: #00897B; /* TODO(cjamcl) clean up unused variables. */ @@ -175,6 +178,7 @@ .lh-vars.dark { --color-red-700: var(--color-red); --color-green-700: var(--color-green); + --color-teal-600: var(--color-cyan-500); --color-orange-700: var(--color-orange); --color-black-200: var(--color-black-800); @@ -388,10 +392,11 @@ } /* Node */ -.lh-node { - display: block; +.lh-node__snippet { font-family: var(--monospace-font-family); - color: hsl(174, 100%, 27%); + color: var(--color-teal-600); + font-size: 12px; + line-height: 1.5em; } /* Score */ diff --git a/lighthouse-core/test/lib/page-functions-test.js b/lighthouse-core/test/lib/page-functions-test.js index d04114a0c297..8906eda99bd3 100644 --- a/lighthouse-core/test/lib/page-functions-test.js +++ b/lighthouse-core/test/lib/page-functions-test.js @@ -59,4 +59,37 @@ describe('Page Functions', () => { assert.equal(pageFunctions.getNodeSelector(childEl), 'div#wrapper > div.child'); }); }); + + describe('getNodeLabel', () => { + it('Returns innerText if element has visible text', () => { + const el = dom.createElement('div'); + el.innerText = 'Hello'; + assert.equal(pageFunctions.getNodeLabel(el), 'Hello'); + }); + + it('Falls back to children and alt/aria-label if a title can\'t be determined', () => { + const el = dom.createElement('div'); + const childEl = dom.createElement('div', '', {'aria-label': 'Something'}); + el.appendChild(childEl); + assert.equal(pageFunctions.getNodeLabel(el), 'Something'); + }); + + it('Truncates long text', () => { + const el = dom.createElement('div'); + el.setAttribute('alt', Array(100).fill('a').join('')); + assert.equal(pageFunctions.getNodeLabel(el).length, 80); + }); + + it('Uses tag name for html tags', () => { + const el = dom.createElement('html'); + assert.equal(pageFunctions.getNodeLabel(el), 'html'); + }); + + it('Uses tag name if there is no better label', () => { + const el = dom.createElement('div'); + const childEl = dom.createElement('span'); + el.appendChild(childEl); + assert.equal(pageFunctions.getNodeLabel(el), 'div'); + }); + }); }); diff --git a/lighthouse-core/test/results/artifacts/artifacts.json b/lighthouse-core/test/results/artifacts/artifacts.json index 93732cd0f466..66b699a1fbe6 100644 --- a/lighthouse-core/test/results/artifacts/artifacts.json +++ b/lighthouse-core/test/results/artifacts/artifacts.json @@ -1096,7 +1096,7 @@ ], "description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds", "help": "Elements must have sufficient color contrast", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/color-contrast?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/color-contrast?application=axeAPI", "nodes": [ { "impact": "serious", @@ -1106,7 +1106,8 @@ ], "failureSummary": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 18.0pt, font weight: bold). Expected contrast ratio of 3:1", "path": "3,HTML,1,BODY,5,DIV,0,H2", - "snippet": "<h2>" + "snippet": "<h2>", + "nodeLabel": "Do better web tester page" }, { "impact": "serious", @@ -1116,7 +1117,8 @@ ], "failureSummary": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 12.0pt, font weight: normal). Expected contrast ratio of 4.5:1", "path": "3,HTML,1,BODY,5,DIV,1,SPAN", - "snippet": "<span>" + "snippet": "<span>", + "nodeLabel": "Hi there!" } ] }, @@ -1130,7 +1132,7 @@ ], "description": "Ensures every HTML document has a lang attribute", "help": "<html> element must have a lang attribute", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/html-has-lang?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/html-has-lang?application=axeAPI", "nodes": [ { "impact": "serious", @@ -1140,7 +1142,8 @@ ], "failureSummary": "Fix any of the following:\n The <html> element does not have a lang attribute", "path": "3,HTML", - "snippet": "<html manifest=\"clock.appcache\">" + "snippet": "<html manifest=\"clock.appcache\">", + "nodeLabel": "html" } ] }, @@ -1156,7 +1159,7 @@ ], "description": "Ensures <img> elements have alternate text or a role of none or presentation", "help": "Images must have alternate text", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/image-alt?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/image-alt?application=axeAPI", "nodes": [ { "impact": "critical", @@ -1166,7 +1169,8 @@ ], "failureSummary": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", "path": "3,HTML,1,BODY,10,IMG", - "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"57\">" + "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"57\">", + "nodeLabel": "img" }, { "impact": "critical", @@ -1176,27 +1180,30 @@ ], "failureSummary": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", "path": "3,HTML,1,BODY,12,IMG", - "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"318\">" + "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"318\">", + "nodeLabel": "img" }, { "impact": "critical", "html": "<img src=\"lighthouse-rotating.gif\" width=\"811\" height=\"462\">", "target": [ - "img[src$=\"lighthouse-rotating.gif\"]" + "img[src$=\"lighthouse-rotating\\.gif\"]" ], "failureSummary": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", "path": "3,HTML,1,BODY,14,IMG", - "snippet": "<img src=\"lighthouse-rotating.gif\" width=\"811\" height=\"462\">" + "snippet": "<img src=\"lighthouse-rotating.gif\" width=\"811\" height=\"462\">", + "nodeLabel": "img" }, { "impact": "critical", "html": "<img src=\"blob:http://localhost:62824/289254fd-ef1d-4c1a-96a8-ba291caa2140\">", "target": [ - "img:nth-child(27)" + "img:nth-child(30)" ], "failureSummary": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,49,IMG", - "snippet": "<img src=\"blob:http://localhost:62824/289254fd-ef1d-4c1a-96a8-ba291caa2140\">" + "path": "3,HTML,1,BODY,53,IMG", + "snippet": "<img src=\"blob:http://localhost:52281/459dbcc0-ca5f-47ef-b78f-e04d79c9e3fa\">", + "nodeLabel": "img" } ] }, @@ -1213,7 +1220,7 @@ ], "description": "Ensures every form element has a label", "help": "Form elements must have labels", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/label?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/label?application=axeAPI", "nodes": [ { "impact": "critical", @@ -1222,18 +1229,20 @@ "input[onpaste=\"event\\.preventDefault\\(\\)\\;\"]" ], "failureSummary": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,44,INPUT", - "snippet": "<input type=\"password\" onpaste=\"event.preventDefault();\">" + "path": "3,HTML,1,BODY,48,INPUT", + "snippet": "<input type=\"password\" onpaste=\"event.preventDefault();\">", + "nodeLabel": "input" }, { "impact": "critical", "html": "<input type=\"password\">", "target": [ - "input:nth-child(25)" + "input:nth-child(28)" ], "failureSummary": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,46,INPUT", - "snippet": "<input type=\"password\">" + "path": "3,HTML,1,BODY,50,INPUT", + "snippet": "<input type=\"password\">", + "nodeLabel": "input" }, { "impact": "critical", @@ -1242,8 +1251,9 @@ "input[onpaste=\"return\\ false\\;\"]" ], "failureSummary": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,48,INPUT", - "snippet": "<input type=\"password\" onpaste=\"return false;\">" + "path": "3,HTML,1,BODY,52,INPUT", + "snippet": "<input type=\"password\" onpaste=\"return false;\">", + "nodeLabel": "input" } ] }, @@ -1260,27 +1270,29 @@ ], "description": "Ensures links have discernible text", "help": "Links must have discernible text", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/link-name?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/link-name?application=axeAPI", "nodes": [ { "impact": "serious", "html": "<a href=\"javascript:void(0)\" target=\"_blank\"></a>", "target": [ - "a[href=\"javascript:void(0)\"]" + "a[href=\"javascript\\:void\\(0\\)\"]" ], "failureSummary": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,40,A", - "snippet": "<a href=\"javascript:void(0)\" target=\"_blank\">" + "path": "3,HTML,1,BODY,44,A", + "snippet": "<a href=\"javascript:void(0)\" target=\"_blank\">", + "nodeLabel": "a" }, { "impact": "serious", "html": "<a href=\"mailto:inbox@email.com\" target=\"_blank\"></a>", "target": [ - "a[href$=\"mailto:inbox@email.com\"]" + "a[href$=\"mailto\\:inbox\\@email\\.com\"]" ], "failureSummary": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,42,A", - "snippet": "<a href=\"mailto:inbox@email.com\" target=\"_blank\">" + "path": "3,HTML,1,BODY,46,A", + "snippet": "<a href=\"mailto:inbox@email.com\" target=\"_blank\">", + "nodeLabel": "a" } ] }, @@ -1296,7 +1308,7 @@ ], "description": "Ensures <object> elements have alternate text", "help": "<object> elements must have alternate text", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/object-alt?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/object-alt?application=axeAPI", "nodes": [ { "impact": "serious", @@ -1304,9 +1316,10 @@ "target": [ "#\\35 934a" ], - "failureSummary": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty", + "failureSummary": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", "path": "3,HTML,1,BODY,2,OBJECT", - "snippet": "<object id=\"5934a\">" + "snippet": "<object id=\"5934a\">", + "nodeLabel": "object" }, { "impact": "serious", @@ -1314,9 +1327,10 @@ "target": [ "#\\35 934b" ], - "failureSummary": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty", + "failureSummary": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", "path": "3,HTML,1,BODY,3,OBJECT", - "snippet": "<object id=\"5934b\">" + "snippet": "<object id=\"5934b\">", + "nodeLabel": "object" } ] } @@ -1358,7 +1372,7 @@ ], "description": "Ensures elements with ARIA roles have all required ARIA attributes", "help": "Required ARIA attributes must be provided", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-required-attr?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-required-attr?application=axeAPI", "nodes": [] }, { @@ -1371,7 +1385,7 @@ ], "description": "Ensures elements with an ARIA role that require child roles contain them", "help": "Certain ARIA roles must contain particular children", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-required-children?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-required-children?application=axeAPI", "nodes": [] }, { @@ -1384,7 +1398,7 @@ ], "description": "Ensures elements with an ARIA role that require parent roles are contained by them", "help": "Certain ARIA roles must be contained by particular parents", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-required-parent?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-required-parent?application=axeAPI", "nodes": [] }, { @@ -1397,7 +1411,7 @@ ], "description": "Ensures all elements with a role attribute use a valid value", "help": "ARIA roles used must conform to valid values", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-roles?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-roles?application=axeAPI", "nodes": [] }, { @@ -1410,7 +1424,7 @@ ], "description": "Ensures all ARIA attributes have valid values", "help": "ARIA attributes must conform to valid values", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-valid-attr-value?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-valid-attr-value?application=axeAPI", "nodes": [] }, { @@ -1423,7 +1437,7 @@ ], "description": "Ensures attributes that begin with aria- are valid ARIA attributes", "help": "ARIA attributes must conform to valid names", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/aria-valid-attr?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/aria-valid-attr?application=axeAPI", "nodes": [] }, { @@ -1466,7 +1480,7 @@ ], "description": "Ensures <dl> elements are structured correctly", "help": "<dl> elements must only directly contain properly-ordered <dt> and <dd> groups, <script> or <template> elements", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/definition-list?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/definition-list?application=axeAPI", "nodes": [] }, { @@ -1521,7 +1535,7 @@ ], "description": "Ensures <iframe> and <frame> elements contain a non-empty title attribute", "help": "Frames must have title attribute", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/frame-title?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/frame-title?application=axeAPI", "nodes": [] }, { @@ -1562,7 +1576,7 @@ ], "description": "Ensures <input type=\"image\"> elements have alternate text", "help": "Image buttons must have alternate text", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/input-image-alt?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/input-image-alt?application=axeAPI", "nodes": [] }, { @@ -1575,7 +1589,7 @@ ], "description": "Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute", "help": "Layout tables must not use data table elements", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/layout-table?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/layout-table?application=axeAPI", "nodes": [] }, { @@ -1588,7 +1602,7 @@ ], "description": "Ensures that lists are structured correctly", "help": "<ul> and <ol> must only directly contain <li>, <script> or <template> elements", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/list?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/list?application=axeAPI", "nodes": [] }, { @@ -1601,7 +1615,7 @@ ], "description": "Ensures <li> elements are used semantically", "help": "<li> elements must be contained in a <ul> or <ol>", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/listitem?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/listitem?application=axeAPI", "nodes": [] }, { @@ -1617,7 +1631,7 @@ ], "description": "Ensures <meta http-equiv=\"refresh\"> is not used", "help": "Timed refresh must not exist", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/meta-refresh?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/meta-refresh?application=axeAPI", "nodes": [] }, { @@ -1629,7 +1643,7 @@ ], "description": "Ensures tabindex attribute values are not greater than 0", "help": "Elements should not have tabindex greater than zero", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/tabindex?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/tabindex?application=axeAPI", "nodes": [] }, { @@ -1644,7 +1658,7 @@ ], "description": "Ensure that each cell in a table using the headers refers to another cell in that table", "help": "All cells in a table element that use the headers attribute must only refer to other cells of that same table", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/td-headers-attr?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/td-headers-attr?application=axeAPI", "nodes": [] }, { @@ -1659,7 +1673,7 @@ ], "description": "Ensure that each table header in a data table refers to data cells", "help": "All th elements and elements with role=columnheader/rowheader must have data cells they describe", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/th-has-data-cells?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/th-has-data-cells?application=axeAPI", "nodes": [] }, { @@ -1672,7 +1686,7 @@ ], "description": "Ensures lang attributes have valid values", "help": "lang attribute must have a valid value", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/valid-lang?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/valid-lang?application=axeAPI", "nodes": [] }, { @@ -1687,7 +1701,7 @@ ], "description": "Ensures <video> elements have captions", "help": "<video> elements must have captions", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/video-caption?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/video-caption?application=axeAPI", "nodes": [] }, { @@ -1702,7 +1716,7 @@ ], "description": "Ensures <video> elements have audio descriptions", "help": "<video> elements must have an audio description track", - "helpUrl": "https://dequeuniversity.com/rules/axe/3.1/video-description?application=axeAPI", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.2/video-description?application=axeAPI", "nodes": [] } ] @@ -2017,6 +2031,7 @@ "snippet": "<button class=\"small-button\">Do something</button>", "path": "3,HTML,1,BODY,17,BUTTON", "selector": "body > button.small-button", + "nodeLabel": "Do something", "href": "" }, { @@ -2033,6 +2048,7 @@ "snippet": "<button class=\"small-button\">Do something else</button>", "path": "3,HTML,1,BODY,18,BUTTON", "selector": "body > button.small-button", + "nodeLabel": "Do something else", "href": "" } ], diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 19844e8e8249..a76abb65aef6 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1598,7 +1598,8 @@ "selector": "h2", "path": "3,HTML,1,BODY,5,DIV,0,H2", "snippet": "<h2>Do better web tester page</h2>", - "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 18.0pt, font weight: bold). Expected contrast ratio of 3:1" + "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 18.0pt, font weight: bold). Expected contrast ratio of 3:1", + "nodeLabel": "Do better web tester page" } }, { @@ -1607,7 +1608,8 @@ "selector": "span", "path": "3,HTML,1,BODY,5,DIV,1,SPAN", "snippet": "<span>Hi there!</span>", - "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 12.0pt, font weight: normal). Expected contrast ratio of 4.5:1" + "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 12.0pt, font weight: normal). Expected contrast ratio of 4.5:1", + "nodeLabel": "Hi there!" } } ], @@ -1689,7 +1691,8 @@ "selector": "html", "path": "3,HTML", "snippet": "<html manifest=\"clock.appcache\">", - "explanation": "Fix any of the following:\n The <html> element does not have a lang attribute" + "explanation": "Fix any of the following:\n The <html> element does not have a lang attribute", + "nodeLabel": "html" } } ], @@ -1733,7 +1736,8 @@ "selector": "img[height=\"\\35 7\"]", "path": "3,HTML,1,BODY,10,IMG", "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"57\">", - "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img" } }, { @@ -1742,25 +1746,28 @@ "selector": "img[height=\"\\33 18\"]", "path": "3,HTML,1,BODY,12,IMG", "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"318\">", - "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img" } }, { "node": { "type": "node", - "selector": "img[src$=\"lighthouse-rotating.gif\"]", + "selector": "img[src$=\"lighthouse-rotating\\.gif\"]", "path": "3,HTML,1,BODY,14,IMG", "snippet": "<img src=\"lighthouse-rotating.gif\" width=\"811\" height=\"462\">", - "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img" } }, { "node": { "type": "node", - "selector": "img:nth-child(27)", - "path": "3,HTML,1,BODY,49,IMG", + "selector": "img:nth-child(30)", + "path": "3,HTML,1,BODY,53,IMG", "snippet": "<img src=\"blob:http://localhost:62824/289254fd-ef1d-4c1a-96a8-ba291caa2140\">", - "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img" } } ], @@ -1804,27 +1811,30 @@ "node": { "type": "node", "selector": "input[onpaste=\"event\\.preventDefault\\(\\)\\;\"]", - "path": "3,HTML,1,BODY,44,INPUT", + "path": "3,HTML,1,BODY,48,INPUT", "snippet": "<input type=\"password\" onpaste=\"event.preventDefault();\">", - "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty" + "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", + "nodeLabel": "input" } }, { "node": { "type": "node", - "selector": "input:nth-child(25)", - "path": "3,HTML,1,BODY,46,INPUT", + "selector": "input:nth-child(28)", + "path": "3,HTML,1,BODY,50,INPUT", "snippet": "<input type=\"password\">", - "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty" + "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", + "nodeLabel": "input" } }, { "node": { "type": "node", "selector": "input[onpaste=\"return\\ false\\;\"]", - "path": "3,HTML,1,BODY,48,INPUT", + "path": "3,HTML,1,BODY,52,INPUT", "snippet": "<input type=\"password\" onpaste=\"return false;\">", - "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty" + "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", + "nodeLabel": "input" } } ], @@ -1868,19 +1878,21 @@ { "node": { "type": "node", - "selector": "a[href=\"javascript:void(0)\"]", - "path": "3,HTML,1,BODY,40,A", + "selector": "a[href=\"javascript\\:void\\(0\\)\"]", + "path": "3,HTML,1,BODY,44,A", "snippet": "<a href=\"javascript:void(0)\" target=\"_blank\"></a>", - "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "a" } }, { "node": { "type": "node", - "selector": "a[href$=\"mailto:inbox@email.com\"]", - "path": "3,HTML,1,BODY,42,A", + "selector": "a[href$=\"mailto\\:inbox\\@email\\.com\"]", + "path": "3,HTML,1,BODY,46,A", "snippet": "<a href=\"mailto:inbox@email.com\" target=\"_blank\"></a>", - "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"" + "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "a" } } ], @@ -1953,7 +1965,8 @@ "selector": "#\\35 934a", "path": "3,HTML,1,BODY,2,OBJECT", "snippet": "<object id=\"5934a\"></object>", - "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty" + "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "object" } }, { @@ -1962,7 +1975,8 @@ "selector": "#\\35 934b", "path": "3,HTML,1,BODY,3,OBJECT", "snippet": "<object id=\"5934b\"></object>", - "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty" + "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "object" } } ], @@ -3113,13 +3127,15 @@ "type": "node", "snippet": "<button class=\"small-button\">Do something</button>", "path": "3,HTML,1,BODY,17,BUTTON", - "selector": "body > button.small-button" + "selector": "body > button.small-button", + "nodeLabel": "Do something" }, "overlappingTarget": { "type": "node", "snippet": "<button class=\"small-button\">Do something else</button>", "path": "3,HTML,1,BODY,18,BUTTON", - "selector": "body > button.small-button" + "selector": "body > button.small-button", + "nodeLabel": "Do something else" }, "tapTargetScore": 864, "overlappingTargetScore": 720, diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index 3ba37e774a69..0196d5311aec 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -199,6 +199,7 @@ { "node": { "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 18.0pt, font weight: bold). Expected contrast ratio of 3:1", + "nodeLabel": "Do better web tester page", "path": "3,HTML,1,BODY,5,DIV,0,H2", "selector": "h2", "snippet": "<h2>Do better web tester page</h2>", @@ -208,6 +209,7 @@ { "node": { "explanation": "Fix any of the following:\n Element has insufficient color contrast of 1.32 (foreground color: #ffc0cb, background color: #eeeeee, font size: 12.0pt, font weight: normal). Expected contrast ratio of 4.5:1", + "nodeLabel": "Hi there!", "path": "3,HTML,1,BODY,5,DIV,1,SPAN", "selector": "span", "snippet": "<span>Hi there!</span>", @@ -871,6 +873,7 @@ { "node": { "explanation": "Fix any of the following:\n The <html> element does not have a lang attribute", + "nodeLabel": "html", "path": "3,HTML", "selector": "html", "snippet": "<html manifest=\"clock.appcache\">", @@ -924,6 +927,7 @@ { "node": { "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img", "path": "3,HTML,1,BODY,10,IMG", "selector": "img[height=\"\\35 7\"]", "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"57\">", @@ -933,6 +937,7 @@ { "node": { "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img", "path": "3,HTML,1,BODY,12,IMG", "selector": "img[height=\"\\33 18\"]", "snippet": "<img src=\"lighthouse-480x318.jpg\" width=\"480\" height=\"318\">", @@ -942,8 +947,9 @@ { "node": { "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "img", "path": "3,HTML,1,BODY,14,IMG", - "selector": "img[src$=\"lighthouse-rotating.gif\"]", + "selector": "img[src$=\"lighthouse-rotating\\.gif\"]", "snippet": "<img src=\"lighthouse-rotating.gif\" width=\"811\" height=\"462\">", "type": "node" } @@ -951,8 +957,9 @@ { "node": { "explanation": "Fix any of the following:\n Element does not have an alt attribute\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,49,IMG", - "selector": "img:nth-child(27)", + "nodeLabel": "img", + "path": "3,HTML,1,BODY,53,IMG", + "selector": "img:nth-child(30)", "snippet": "<img src=\"blob:http://localhost:62824/289254fd-ef1d-4c1a-96a8-ba291caa2140\">", "type": "node" } @@ -1142,7 +1149,8 @@ { "node": { "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,44,INPUT", + "nodeLabel": "input", + "path": "3,HTML,1,BODY,48,INPUT", "selector": "input[onpaste=\"event\\.preventDefault\\(\\)\\;\"]", "snippet": "<input type=\"password\" onpaste=\"event.preventDefault();\">", "type": "node" @@ -1151,8 +1159,9 @@ { "node": { "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,46,INPUT", - "selector": "input:nth-child(25)", + "nodeLabel": "input", + "path": "3,HTML,1,BODY,50,INPUT", + "selector": "input:nth-child(28)", "snippet": "<input type=\"password\">", "type": "node" } @@ -1160,7 +1169,8 @@ { "node": { "explanation": "Fix any of the following:\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Form element does not have an implicit (wrapped) <label>\n Form element does not have an explicit <label>\n Element has no title attribute or the title attribute is empty", - "path": "3,HTML,1,BODY,48,INPUT", + "nodeLabel": "input", + "path": "3,HTML,1,BODY,52,INPUT", "selector": "input[onpaste=\"return\\ false\\;\"]", "snippet": "<input type=\"password\" onpaste=\"return false;\">", "type": "node" @@ -1207,8 +1217,9 @@ { "node": { "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,40,A", - "selector": "a[href=\"javascript:void(0)\"]", + "nodeLabel": "a", + "path": "3,HTML,1,BODY,44,A", + "selector": "a[href=\"javascript\\:void\\(0\\)\"]", "snippet": "<a href=\"javascript:void(0)\" target=\"_blank\"></a>", "type": "node" } @@ -1216,8 +1227,9 @@ { "node": { "explanation": "Fix all of the following:\n Element is in tab order and does not have accessible text\n\nFix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", - "path": "3,HTML,1,BODY,42,A", - "selector": "a[href$=\"mailto:inbox@email.com\"]", + "nodeLabel": "a", + "path": "3,HTML,1,BODY,46,A", + "selector": "a[href$=\"mailto\\:inbox\\@email\\.com\"]", "snippet": "<a href=\"mailto:inbox@email.com\" target=\"_blank\"></a>", "type": "node" } @@ -1952,7 +1964,8 @@ "items": [ { "node": { - "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty", + "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "object", "path": "3,HTML,1,BODY,2,OBJECT", "selector": "#\\35 934a", "snippet": "<object id=\"5934a\"></object>", @@ -1961,7 +1974,8 @@ }, { "node": { - "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty", + "explanation": "Fix any of the following:\n Element does not have text that is visible to screen readers\n aria-label attribute does not exist or is empty\n aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n Element has no title attribute or the title attribute is empty\n Element's default semantics were not overridden with role=\"presentation\"\n Element's default semantics were not overridden with role=\"none\"", + "nodeLabel": "object", "path": "3,HTML,1,BODY,3,OBJECT", "selector": "#\\35 934b", "snippet": "<object id=\"5934b\"></object>", @@ -2481,6 +2495,7 @@ "height": 18.0, "overlapScoreRatio": 0.8333333333333334, "overlappingTarget": { + "nodeLabel": "Do something else", "path": "3,HTML,1,BODY,18,BUTTON", "selector": "body > button.small-button", "snippet": "<button class=\"small-button\">Do something else</button>", @@ -2489,6 +2504,7 @@ "overlappingTargetScore": 720.0, "size": "200x18", "tapTarget": { + "nodeLabel": "Do something", "path": "3,HTML,1,BODY,17,BUTTON", "selector": "body > button.small-button", "snippet": "<button class=\"small-button\">Do something</button>", diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index f28e7b1dc24b..860a0520f633 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -145,6 +145,7 @@ declare global { snippet: string; target: string[]; failureSummary?: string; + nodeLabel?: string; }[]; }[]; notApplicable: { @@ -337,11 +338,12 @@ declare global { } export interface TapTarget { - snippet: string, - selector: string, - path: string, - href: string, - clientRects: Rect[] + snippet: string; + selector: string; + nodeLabel?: string; + path: string; + href: string; + clientRects: Rect[]; } export interface ViewportDimensions { diff --git a/types/audit-details.d.ts b/types/audit-details.d.ts index ca670c89a48f..155d52ae716f 100644 --- a/types/audit-details.d.ts +++ b/types/audit-details.d.ts @@ -160,7 +160,10 @@ declare global { type: 'node'; path?: string; selector?: string; + /** An HTML snippet used to identify the node. */ snippet?: string; + /** A human-friendly text descriptor that's used to identify the node more quickly. */ + nodeLabel?: string; } /**