Running axe-core in the browser console
Published: | Author: Russ Weakley
What this tutorial is about
This tutorial will help you understand how axe-core works by running it directly in the browser console. You'll see how axe-core produces accessibility test results and how tools like axe DevTools build on this functionality to make testing easier.
Tools like browser extensions can seem a bit magical. But under the surface, axe-core just produces a set of results. How you look at those results, filter them, report on them and act on them is up to you.
About axe-core
axe-core is a free, open-source accessibility testing engine. It's the engine inside tools like the axe DevTools browser extension.
In this tutorial, you'll run axe-core directly from Chrome's browser console. There's nothing to install, and no extension needed.
The basic idea behind axe tools:
- Load a page.
- Run an accessibility test.
- Do something with the results.
Every axe tool, from browser extensions to automated test suites, follows these same three basic steps.
Before you start
You'll need Google Chrome. There's nothing to install.
If you'd like to try the optional axe DevTools exercise at the end, you'll also need the axe DevTools browser extension.
Why start with the browser console?
Most teams don't use the browser console for everyday accessibility testing. However, it's a useful way to understand how axe-core works before moving on to running tests from the command line.
What's in this tutorial
Getting started
- Step 1. Open the test page
- Step 2. Open the console
- Step 3. Load axe-core into the page
- Step 4. Check axe has loaded
- Step 5. Run axe
- Step 6. Look inside the results
Exploring the results
- Count the failed rules
- Show the violations as a table
- Filter to critical and serious
- See which elements failed
- Highlight the failing elements
- Copy the results
- Open a full report in a new tab
Trying another page
Getting started
Step 1. Open the test page
Open the axe test page in Chrome, since its console is the easiest to work with.
The page has 7 deliberately planted accessibility errors. The table below explains each issue, so you can compare them with axe-core's results.
| Error | What's wrong |
|---|---|
| 1 | The page doesn't say what language it's in |
| 2 | A heading jumps from level 1 to level 3 |
| 3 | An image has no alt text |
| 4 | A text box has no label |
| 5 | A button has no text |
| 6 | A link has no text |
| 7 | Some grey text has low contrast |
You should see a heading, a placeholder image, an empty text box, an empty button and an empty link, and some faint grey text. The button and link have red borders, so you can see where they are, even though they have no text.
Step 2. Open the console
Open Chrome's developer tools and go to the Console tab. This is where you'll run JavaScript directly in the browser.
- Mac: Command + Option + J
- Windows: Ctrl + Shift + J
Watch out for:
- Click on the web page first. Otherwise the shortcut may go to a different app.
- If the shortcut doesn't work, right-click anywhere on the page, choose Inspect, then click the Console tab.
Step 3. Load axe-core into the page
Paste this code into the console and press Enter. It adds the axe-core script to the page. This works much like adding a <script> element to the HTML, but you're doing it while the page is open.
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/axe-core@4.14.0/axe.min.js';
document.head.appendChild(script);
In plain terms:
- Make a new
<script>element. - Set its source to the axe-core file, which is hosted online.
- Add it to the page, so the browser downloads and runs it.
Watch out for:
- Chrome may block your first paste. If it does, type
allow pasting, press Enter, then paste again. - The address includes a version number (
axe-core@4.14.0). This helps keep the test results consistent, even when newer versions of axe-core are released.
Step 4. Check axe has loaded
Type this and press Enter:
axe.version
You should see '4.14.0'. This means axe-core has loaded into the page and is ready to run accessibility tests.
Watch out for:
- If you see
axe is not defined, the file may still be downloading. Wait a couple of seconds and try again.
Step 5. Run axe
Paste this and press Enter:
const results = await axe.run();
In plain terms: run axe on the whole page, wait for it to finish, and store the findings in something called results.
Watch out for:
- The console may show
undefined. This is normal. Your results have been stored, ready for the next step.
Step 6. Look inside the results
Type this and press Enter:
results
Click the small triangle at the start of the line to expand it. Near the bottom, you'll find four groups, each with a count in brackets:
- violations: rules that found accessibility issues (7)
- passes: rules that passed
- incomplete: checks axe couldn't complete or confirm, which may need a human to review
- inapplicable: rules with nothing to check on this page, such as table rules on a page with no tables
These four groups help you understand what axe-core found. It doesn't tell you whether a page is fully accessible, only what its automated checks could and couldn't confirm.
Watch out for:
- The groups are listed in alphabetical order, mixed in with other details such as the page address and the time of the test.
- The rules under inapplicable may look like errors because they're described as accessibility requirements. They're not errors. They simply don't apply to this page.
Video walkthrough: Getting started
Transcript
- Title: Getting started
- Step 1. Open the test page
- Step 2. Open the console
- Step 3. Load axe-core into the page
- Step 4. Check axe has loaded
- Step 5. Run axe
- Step 6. Look inside the results
Exploring the results
The following examples are optional. They show different ways to explore and present the results, and you can try them in any order.
Count the failed rules
Type this and press Enter:
results.violations.length
In plain terms: from results, find the violations group and use length to count how many items it contains. Each dot lets you access another part of the results.
You should see 7, one for each planted error.
Watch out for:
- This counts the number of rules that failed, not individual problems. Three images with no alt text would still count as one failed rule.
Show the violations as a table
Paste this and press Enter:
console.table(results.violations.map(v => ({
rule: v.id,
impact: v.impact,
instances: v.nodes.length,
help: v.help
})));
In plain terms: for each violation, find the rule's ID, how severe the issue is, how many elements are affected and a short description. Then show everything in a table.
You should see a table with 7 rows: 3 critical, 3 serious and 1 moderate.
Watch out for:
- axe lists rules alphabetically, not by severity or by where they appear on the page.
Filter to critical and serious
Paste this and press Enter:
results.violations
.filter(v => v.impact === 'critical' || v.impact === 'serious')
.forEach(v => console.log(v.impact, v.id));
In plain terms: keep only the violations that are critical or serious, then print each one on its own line.
You should see 6 lines. You won't see heading-order because its impact is moderate.
axe-core reports all the violations it finds. Tools that let you filter by severity use this information to decide which results to show.
Watch out for:
- axe-core has four impact levels: critical, serious, moderate and minor. There's no "high".
See which elements failed
Paste this and press Enter:
results.violations.forEach(v =>
v.nodes.forEach(n => console.log(v.id, '|', n.target.join(' '), '|', n.html))
);
In plain terms: for each violation, go through every affected element and print the rule's ID, the element's CSS selector (the target) and its HTML.
You should see 7 lines, each showing the HTML of an affected element from your test page, such as <img src="https://placehold.co/200x200"> on the image-alt line. This answers the "where on the page?" question.
Watch out for:
- On this small page, each target is just an element name like
img. On a real site, selectors are often longer to help identify specific elements.
Highlight the failing elements
Highlight the affected elements on the page, rather than just viewing them in the console. Paste this and press Enter:
results.violations.forEach(v =>
v.nodes.forEach(n => {
const el = document.querySelector(n.target[0]);
if (el) el.style.outline = '4px dashed red';
})
);
In plain terms: for each affected element, find it on the page using its CSS selector (the target), and draw a thick, dashed red outline around it.
You should see dashed red outlines around the seven affected elements: the heading, image, text box, button, link, faint text and the root HTML element (for the missing lang).
Watch out for:
- The outlines are temporary. Reload the page to remove them.
- Highlighting should only change how elements look, not their accessibility. For example, adding a
titleattribute could give an empty button an accessible name and change the test results.
Copy the results
Chrome's console has a built-in copy() command. Type this and press Enter:
copy(results)
In plain terms: copy everything in results to the clipboard.
The console will show undefined. Now paste the results into a text editor using Command + V on Mac or Ctrl + V on Windows. You'll see the full results as JSON.
This is the same kind of data that other axe tools work with. For example, the axe command-line tool can save accessibility test results as JSON. Both approaches use axe-core to run the tests.
Watch out for:
copy()is a Chrome developer tools command. It works in the console, but not in a web page's own code or a bookmarklet.- The results can be long. Paste them into a code editor rather than a chat or email.
Open a full report in a new tab
Turn the results into a readable report and open it in a new browser tab. Paste this and press Enter:
{
const escape = s => String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
const order = { critical: 0, serious: 1, moderate: 2, minor: 3 };
const violations = [...results.violations].sort((a, b) => order[a.impact] - order[b.impact]);
const sections = violations.map(v => `
<section>
<h2>${escape(v.help)}</h2>
<p><strong>Impact:</strong> ${escape(v.impact)}. <strong>Rule:</strong> <code>${escape(v.id)}</code></p>
<p>${escape(v.description)}</p>
<h3>Failing elements</h3>
<ul>
${v.nodes.map(n => `
<li>
<pre><code>${escape(n.html)}</code></pre>
<pre>${escape(n.failureSummary)}</pre>
</li>`).join('')}
</ul>
<p><a href="${escape(v.helpUrl)}">Learn more about ${escape(v.id)}</a></p>
</section>`).join('');
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>axe report</title>
<style>
body { font-family: system-ui, sans-serif; line-height: 1.5; max-width: 60rem; margin: 2rem auto; padding: 0 1rem; }
section { border-top: 2px solid #ccc; margin-top: 2rem; }
pre { background: #f4f4f4; padding: 0.75rem; white-space: pre-wrap; }
</style>
</head>
<body>
<main>
<h1>axe report</h1>
<p>Page: <code>${escape(results.url)}</code></p>
<p>${results.violations.length} violations, ${results.passes.length} passes, ${results.incomplete.length} need review.</p>
${sections}
</main>
</body>
</html>`;
window.open(URL.createObjectURL(new Blob([html], { type: 'text/html' })));
}
In plain terms:
escapemakes sure HTML from the results is displayed as text, rather than becoming part of the report's HTML.- The violations are sorted from critical to minor.
- Each violation becomes a section, with its description, impact, affected elements, details about what failed and a help link.
- The sections are added to a simple web page.
window.openopens that page in a new tab.
You should see a new tab called "axe report", listing all 7 violations, with the critical ones first.
Watch out for:
- If nothing opens, Chrome may have blocked the new tab as a pop-up. Look for a blocked pop-up icon at the right of the address bar, and choose to allow pop-ups for this page.
- The report isn't saved automatically. To keep it, save the page (Command + S on Mac, Ctrl + S on Windows) while you're on the report tab.
- The curly brackets around the code mean you can paste it again without causing an error.
Video walkthrough: Exploring the results
Transcript
- Title: Exploring the results
- Count the failed rules
- Show the violations as a table
- Filter to critical and serious
- See which elements failed
- Highlight the failing elements
- Copy the results
- Open a full report in a new tab
Trying another page
Load and run axe-core on a new page
Now let's try axe-core on another page. There's a fixed version of the test page, with all seven accessibility issues corrected.
Open the fixed page in Chrome, then open the console.
Here's a quicker way to load axe-core and run the test. Instead of entering two separate commands, you can combine them into one:
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/axe-core@4.14.0/axe.min.js';
const loaded = new Promise(resolve => script.onload = resolve);
document.head.appendChild(script);
await loaded;
const results = await axe.run();
Paste this into the console and press Enter.
The code loads axe-core, waits until it's ready, then runs the accessibility test and stores the results in results.
You can use this same approach on other pages, without having to enter the commands separately.
Watch out for:
- Some websites block external scripts as a security measure. If you see an error mentioning Content Security Policy, the site may be blocking axe-core from loading.
Check the results
Let's see what axe-core found on the fixed page.
Type this and press Enter:
results.violations.length
You should see 0, meaning axe-core found no violations.
Now check how many rules passed:
results.passes.length
You should see more passes than on the original test page, because the corrected elements now pass the rules they failed before.
You can also try any of the examples from Exploring the results on this page.
Watch out for:
0violations doesn't mean the page is fully accessible. axe-core can only identify some accessibility issues automatically. You'll still need to check things like whether alt text makes sense and whether everything works with a keyboard.- The fixed page marks its image as decorative, with
alt="". On a real page, an image that carries meaning needs appropriate alternative text.
Video walkthrough: Trying another page
Transcript
- Title: Trying another page
- Load and run axe-core on a new page
- Check the results
Try it in axe DevTools
If you have the axe DevTools browser extension, run it on both test pages and compare the results. You should see the same issues, because the extension uses axe-core too.
If your counts don't match, check the extension's Best Practices setting. When it's turned off, heading-order won't appear, because it's a best practice rule rather than a WCAG requirement.
Related articles
This tutorial is part of a series on accessibility testing with axe-core and Playwright. The articles in this series are:
- axe-core, Playwright and Cucumber
- Running axe-core in the browser console
- Setting up Node.js
- Running axe-core from the command line
- Running axe-core with a Playwright script COMING SOON
- Accessibility testing with Playwright