import type { AxeResults, NodeResult, RelatedNode } from 'axe-core'; import axe from 'axe-core'; domReady().then(() => { axe.run(document.body, { reporter: 'v2' }).then((results) => { logToConsole(results); }); }); // contrasted against Chrome default color of #ffffff const lightTheme = { serious: '#d93251', minor: '#d24700', text: 'black' }; // contrasted against Safari dark mode color of #535353 const darkTheme = { serious: '#ffb3b3', minor: '#ffd500', text: 'white' }; const theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? darkTheme : lightTheme; const boldCourier = 'font-weight:bold;font-family:Courier;'; const critical = `color:${theme.serious};font-weight:bold;`; const serious = `color:${theme.serious};font-weight:normal;`; const moderate = `color:${theme.minor};font-weight:bold;`; const minor = `color:${theme.minor};font-weight:normal;`; const defaultReset = `font-color:${theme.text};font-weight:normal;`; function logToConsole(results: AxeResults): void { console.group('%cNew axe issues', serious); results.violations.forEach((result) => { let fmt: string; switch (result.impact) { case 'critical': fmt = critical; break; case 'serious': fmt = serious; break; case 'moderate': fmt = moderate; break; case 'minor': fmt = minor; break; default: fmt = minor; break; } console.groupCollapsed( '%c%s: %c%s %s', fmt, result.impact, defaultReset, result.help, result.helpUrl ); result.nodes.forEach((node) => { failureSummary(node, 'any'); failureSummary(node, 'none'); }); console.groupEnd(); }); console.groupEnd(); } function failureSummary(node: NodeResult, key: AxeCoreNodeResultKey): void { if (node[key].length > 0) { logElement(node, console.groupCollapsed); logHtml(node); logFailureMessage(node, key); let relatedNodes: RelatedNode[] = []; node[key].forEach((check) => { relatedNodes = relatedNodes.concat(check.relatedNodes ?? []); }); if (relatedNodes.length > 0) { console.groupCollapsed('Related nodes'); relatedNodes.forEach((relatedNode) => { logElement(relatedNode, console.log); logHtml(relatedNode); }); console.groupEnd(); } console.groupEnd(); } } function logFailureMessage(node: NodeResult, key: AxeCoreNodeResultKey): void { // this exists on axe but we don't export it as part of the typescript // namespace, so just let me use it as I need const message: string = ( axe as unknown as AxeWithAudit )._audit.data.failureSummaries[key].failureMessage( node[key].map((check) => check.message || '') ); console.error(message); } function logElement( node: NodeResult | RelatedNode, logFn: (...args: unknown[]) => void ): void { const el = document.querySelector(node.target.toString()); if (!el) { logFn('Selector: %c%s', boldCourier, node.target.toString()); } else { logFn('Element: %o', el); } } function logHtml(node: NodeResult | RelatedNode): void { console.log('HTML: %c%s', boldCourier, node.html); } type AxeCoreNodeResultKey = 'any' | 'all' | 'none'; interface AxeWithAudit { _audit: { data: { failureSummaries: { any: { failureMessage: (args: string[]) => string; }; all: { failureMessage: (args: string[]) => string; }; none: { failureMessage: (args: string[]) => string; }; }; }; }; } function domReady() { return new Promise((resolve) => { if (document.readyState == 'loading') { document.addEventListener('DOMContentLoaded', () => resolve(), { once: true }); } else { resolve(); } }); }