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': '
\n
',
+ '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': '',
+ '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': '\n
',
+ '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': '\n
',
+ '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': '
',
+ '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': '',
+ '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': '',
+ 'explanation': 'Fix any of the following:\n The 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': ' ',
+ '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': ' ',
+ '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': ' ',
+ '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) \n Form element does not have an explicit \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': ' ',
+ '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': '',
+ 'explanation': 'Fix all of the following:\n List element has direct children that are not allowed inside elements',
+ 'nodeLabel': 'ul',
+ },
+ },
+ ],
},
},
'listitem': {
score: 0,
details: {
- items: {
- length: 1,
- },
+ items: [
+ {
+ node: {
+ 'type': 'node',
+ 'selector': '#listitem',
+ 'snippet': ' ',
+ 'explanation': 'Fix any of the following:\n List item does not have a , or role="list" parent element',
+ 'nodeLabel': 'li',
+ },
+ },
+ ],
},
},
'meta-viewport': {
score: 0,
details: {
- items: {
- length: 1,
- },
+ items: [
+ {
+ node: {
+ 'type': 'node',
+ 'selector': 'meta[name="viewport"]',
+ 'snippet': ' ',
+ 'explanation': 'Fix any of the following:\n user-scalable=no on tag disables zooming on mobile devices',
+ 'nodeLabel': 'meta',
+ },
+ },
+ ],
},
},
'object-alt': {
score: 0,
details: {
- items: {
- length: 1,
- },
+ items: [
+ {
+ node: {
+ 'type': 'node',
+ 'selector': '#object-alt',
+ 'snippet': ' ',
+ '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': '\n
',
+ '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': '',
+ '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': 'foo
',
+ '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': 'Foo ',
+ '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 ',
'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 ',
'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": "