Running axe-core from the command line
Published: | Author: Russ Weakley
What this tutorial is about
In Running axe-core in the browser console, you ran axe-core by hand, one step at a time. In this tutorial, you'll run axe-core with a single command, without opening a browser yourself. You'll also try some useful options, save the results to a file, and turn them into a readable report.
About the axe command line tool
The axe command line tool (@axe-core/cli) is a free tool from Deque, the makers of axe-core. When you give it a web address, it:
- opens the page in an invisible copy of Chrome
- runs axe-core on it
- lists any problems it finds.
These are the same steps you did by hand in the browser console. The tool just does them for you.
Before you start
Complete Setting up Node.js first. You should have:
- Node.js 22.12 or higher
- an
axe-learningproject folder - the axe tools installed in that folder
- Google Chrome
- a matching ChromeDriver.
You'll type the commands in Terminal (Mac) or Command Prompt (Windows).
Why use the command line?
The command line lets you check pages without opening them, check several pages at once, save the results to a file, and make a check pass or fail. It's also a step towards how development teams run axe-core automatically.
Most teams don’t run axe-core from the command line day to day either. Instead, they build it into their automated tests or their CI pipeline, so checks run every time code changes. Learning the command line first makes those setups much easier to understand.
What's in this tutorial
Getting started
Trying different options
Make accessibility tests fail when issues are found
- Understand exit codes
- Make axe report a failure
- Check whether the test passed or failed
- Run the test on the fixed page
Saving and reporting
- Save the results to a file
- Explore the JSON file
- Write a reporting script
- Run the report
- Add a shortcut command
- Run the shortcut command
Getting started
Step 1. Open your project folder
Open Terminal (Mac) or Command Prompt (Windows), then move into the axe-learning folder you made in Setting up Node.js.
Mac:
cd Documents/axe-learning
Windows:
cd %USERPROFILE%\axe-learning
In plain terms: cd stands for "change directory", which means "go into this folder".
To check it worked:
- Mac: type
pwdand press Return. You should see something ending in/Documents/axe-learning. - Windows: type
cdon its own and press Enter. You should see something likeC:\Users\yourname\axe-learning.
Watch out for:
- You'll need to do this every time you open Terminal or Command Prompt. The tools are installed in this folder, so the commands only work from inside it.
- On Windows, use Command Prompt, not PowerShell. If the line where you type starts with
PS, you're in PowerShell.
Step 2. Run axe on the test page
This uses the same axe test page as the browser console tutorial, with 7 deliberately planted accessibility errors. Type the command for your computer and press Enter:
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
In plain terms: "run axe on this page, using the ChromeDriver you installed during setup."
npxruns a tool installed in your project folder. Here, it runsaxe.- The web address is the page to check.
--chromedriver-pathtells axe which ChromeDriver to use. You'll include it every time.
It can take a few seconds. When it's done, you should see 7 Accessibility issues detected.
Watch out for:
- An error mentioning "session not created" means Chrome has updated since you installed ChromeDriver. Repeat the ChromeDriver steps in Setting up Node.js with your new Chrome version.
- Keep the quotes around the ChromeDriver path.
- You need an internet connection, because the test page is online.
Step 3. Read the results
The results will look something like this:
Running axe-core 4.14.0 in chrome-headless
Testing https://intopia.github.io/exercise/axe-core-test-page.html ... please wait, this may take a minute.
Violation of "button-name" with 1 occurrences!
Ensure buttons have discernible text. Correct invalid elements at:
- button
For details, see: https://dequeuniversity.com/rules/axe/4.14/button-name
Violation of "color-contrast" with 1 occurrences!
Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds. Correct invalid elements at:
- .faint
For details, see: https://dequeuniversity.com/rules/axe/4.14/color-contrast
Violation of "heading-order" with 1 occurrences!
Ensure the order of headings is semantically correct. Correct invalid elements at:
- h3
For details, see: https://dequeuniversity.com/rules/axe/4.14/heading-order
Violation of "html-has-lang" with 1 occurrences!
Ensure every HTML document has a lang attribute. Correct invalid elements at:
- html
For details, see: https://dequeuniversity.com/rules/axe/4.14/html-has-lang
Violation of "image-alt" with 1 occurrences!
Ensure img elements have alternative text or a role of none or presentation. Correct invalid elements at:
- img
For details, see: https://dequeuniversity.com/rules/axe/4.14/image-alt
Violation of "label" with 1 occurrences!
Ensure every form element has a label. Correct invalid elements at:
- input
For details, see: https://dequeuniversity.com/rules/axe/4.14/label
Violation of "link-name" with 1 occurrences!
Ensure links have discernible text. Correct invalid elements at:
- a
For details, see: https://dequeuniversity.com/rules/axe/4.14/link-name
7 Accessibility issues detected.
Testing complete of 1 pages
Each block is one failed rule. In plain terms:
"button-name"is the rule that failed.- "with 1 occurrences" is the number of elements that failed it.
- "Ensure buttons have discernible text" describes what the rule checks.
- The line starting with a dash (
- button) is the CSS selector of each element that failed. - "For details, see" links to Deque's help page for the rule.
This is the same information you explored in the browser console, just printed as text.
Watch out for:
- The total at the end counts elements, not rules. On this page, each rule fails on exactly one element, so both numbers are 7. On a real site, three images with no alt text would add 3 to the total.
- The results don't show impact levels (critical, serious and so on), and there's no option to filter by them.
Video walkthrough: Getting started
Transcript
- Title: Getting started
- Step 1. Open your project folder
- Step 2. Run axe on the test page
- Step 3. Read the results
Trying different options
The following examples are optional. They show some of the command line tool's options, and you can try them in any order.
Run WCAG rules only
Add --tags wcag2a,wcag2aa to only run rules for WCAG levels A and AA. This does the same job as turning off the axe DevTools Best Practices setting.
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --tags wcag2a,wcag2aa --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --tags wcag2a,wcag2aa --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
In plain terms: "same as before, but only run rules tagged WCAG 2 level A or AA."
You should see 6 issues. heading-order is gone, because it's a best practice rule, not a WCAG requirement.
Watch out for:
- Don't put spaces after the commas in the tag list.
wcag2a, wcag2aawould break it. - Each WCAG version has its own tags. Testing against WCAG 2.2 AA means listing them all:
wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa.
Test the fixed page
Run axe on the fixed version of the test page, where all 7 errors have been corrected:
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page-fixed.html --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page-fixed.html --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
The results will look something like this:
Running axe-core 4.14.0 in chrome-headless
Testing https://intopia.github.io/exercise/axe-core-test-page-fixed.html ... please wait, this may take a minute.
0 violations found!
Testing complete of 1 pages
Watch out for:
0 violationsdoesn't mean the page is fully accessible. axe-core can only find some accessibility issues automatically. A person still needs to check things like whether alt text makes sense, and whether everything works with a keyboard.
Test more than one page at once
Give the command more than one web address, separated by spaces, and it checks each page in turn:
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html https://intopia.github.io/exercise/axe-core-test-page-fixed.html --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html https://intopia.github.io/exercise/axe-core-test-page-fixed.html --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
In plain terms: "check both pages, one after the other."
You should see the results for each page, then Testing complete of 2 pages.
Watch out for:
- The command line tool checks each page as it first loads. It can't log in, open a menu or fill in a form first. For that, you need a script, which is covered in a future tutorial.
- It also doesn't find pages by itself. You need to list every page you want to check.
Video walkthrough: Trying different options
Transcript
- Title: Trying different options
- Run WCAG rules only
- Test the fixed page
- Test more than one page at once
Make accessibility tests fail when issues are found
Finding accessibility issues is useful. But automated tests can also help prevent those issues from reaching a live website.
Development teams can set up their automated processes to stop a build or block a release when accessibility tests fail. Let's look at how this works.
Understand exit codes
Every command reports a number when it finishes, called an exit code. This tells other programs whether the command succeeded or failed.
0means the command succeeded.- A non-zero number means the command failed or encountered an error.
Automated development tools can read these exit codes and decide what to do next. For example, a team could set up its testing process to prevent code from being released when an accessibility test fails.
Make axe report a failure
By default, the axe command line tool can report success even when it finds accessibility violations. Add --exit to make the command report a failure when violations are found.
You won't see anything different in the test results. The change is in the exit code, which we'll check in the next section.
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --exit --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --exit --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
The results should look the same as before. Although you can't see it yet, the command has reported a failure because accessibility violations were found.
Check whether the test passed or failed
Let's check the exit code from the test you just ran.
Mac:
echo $?
Windows:
echo %errorlevel%
You should see 1, meaning the test reported a failure.
Run the test on the fixed page
Now let's see what happens when you run the same accessibility test on a page without the seven known issues.
Use the following command to test the fixed version of the test page. We've kept --exit, so the command will report whether the test passed or failed.
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page-fixed.html --exit --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page-fixed.html --exit --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
You should see that axe found no accessibility violations.
Now check the exit code:
Mac:
echo $?
Windows:
echo %errorlevel%
You should see 0, meaning the command completed successfully without finding any violations.
You've now seen how the same accessibility test can report a failure on one page and success on another. Development teams can use these exit codes to stop code from being released when accessibility issues are found, if their automated processes are set up to do so.
Watch out for:
- The exit code only tells you whether the last command succeeded or failed. Run the
echocommand straight after the axe command, before running anything else. --exitreports a failure if any accessibility violations are found, regardless of their impact. The Playwright tutorial will show how to fail a test only when critical or serious issues are found.- A non-zero exit code can also mean something went wrong while running the test. Check the results or error messages to understand why the command failed.
Video walkthrough: Make accessibility tests fail when issues are found
Transcript
- Title: Make accessibility tests fail when issues are found
- Understand exit codes
- Make axe report a failure
- Check whether the test passed or failed
- Run the test on the fixed page
Saving and reporting
These examples build on each other, so do them in order.
Save the results to a file
Add --save results.json to save the full results as a file. JSON is a plain-text format for structured data. It's the same results you explored in the browser console, written into a file.
Mac:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --save results.json --chromedriver-path "node_modules/chromedriver/lib/chromedriver/chromedriver"
Windows:
npx axe https://intopia.github.io/exercise/axe-core-test-page.html --save results.json --chromedriver-path "node_modules\chromedriver\lib\chromedriver\chromedriver.exe"
In plain terms: "run axe on the test page, and also save the full results into a file called results.json."
You'll see the same results as before, plus a line starting with Saved file at.
To check it worked, list the files in your folder (ls on Mac, dir on Windows). You should see results.json.
Watch out for:
- Each run replaces the file with the latest results.
- Save the results for one page at a time. If you check several pages, the file holds them all, but the reporting script below only reads the first one.
Explore the JSON file
Open results.json in Chrome, and find the same groups and fields you saw in the console: violations, impact, nodes, tags, help and the "how to fix" summaries.
Mac:
open -a "Google Chrome" results.json
Windows:
start chrome "%cd%\results.json"
Chrome shows the file as one long block of text. Tick the Pretty-print checkbox at the top of the page to lay it out neatly.
Watch out for:
- Scroll down past inapplicable to find violations. It's near the end of the file.
"impact": nullmeans the rule passed or didn't apply. Impact is only filled in when something fails.- On Windows, Chrome needs the full path to the file, which is what
%cd%fills in. Without it, Chrome shows "This site can't be reached".
Write a reporting script
Create a script that reads results.json and turns it into a full HTML report. The report includes the test details, a summary, every violation with its tags and description, the failing elements with "how to fix" text, and a list of things that need a human to review.
Create the file and open it:
Mac:
touch make-report.js
code make-report.js
Windows:
type nul > make-report.js
notepad make-report.js
Paste in the following code, then save the file:
// Load Node's built-in file tools
const fs = require('fs');
const path = require('path');
// 1. Read the JSON file saved by the axe CLI
const raw = fs.readFileSync(path.join(__dirname, 'results.json'), 'utf8');
const data = JSON.parse(raw);
// The CLI saves a list (one item per page tested). We take the first page.
const results = Array.isArray(data) ? data[0] : data;
// 2. Helper: make text safe to show inside HTML
const escape = s => String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
// 3. Sort violations from most to least severe
const order = { critical: 0, serious: 1, moderate: 2, minor: 3 };
const violations = [...results.violations].sort((a, b) => order[a.impact] - order[b.impact]);
// 4. Count violations by impact
const counts = { critical: 0, serious: 0, moderate: 0, minor: 0 };
violations.forEach(v => counts[v.impact]++);
// 5. Build one section per violation
const sections = violations.length === 0
? '<p>No violations found.</p>'
: violations.map(v => `
<section>
<h3>${escape(v.help)}</h3>
<dl>
<dt>Rule</dt><dd><code>${escape(v.id)}</code></dd>
<dt>Impact</dt><dd>${escape(v.impact)}</dd>
<dt>Description</dt><dd>${escape(v.description)}</dd>
<dt>Tags</dt><dd>${v.tags.map(t => `<code>${escape(t)}</code>`).join(', ')}</dd>
<dt>Elements affected</dt><dd>${v.nodes.length}</dd>
</dl>
<p><a href="${escape(v.helpUrl)}">Learn more about ${escape(v.id)} at Deque University</a></p>
<h4>Failing elements</h4>
<ol>
${v.nodes.map(n => `
<li>
<p>Selector: <code>${escape(n.target.join(' '))}</code></p>
<pre><code>${escape(n.html)}</code></pre>
<p>How to fix:</p>
<pre>${escape(n.failureSummary)}</pre>
</li>`).join('')}
</ol>
</section>`).join('');
// 6. Build the "needs review" list from incomplete results
const review = results.incomplete.length === 0
? '<p>Nothing needs review.</p>'
: `<ul>${results.incomplete.map(v =>
`<li><code>${escape(v.id)}</code>: ${escape(v.help)} (${v.nodes.length} elements)</li>`
).join('')}</ul>`;
// 7. Put the whole page together
const env = results.testEnvironment || {};
const report = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Full axe report</title>
<style>
body { font-family: system-ui, sans-serif; line-height: 1.5; max-width: 60rem; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
section { border-top: 2px solid #ccc; padding-top: 1rem; margin-top: 2rem; }
dt { font-weight: bold; }
dd { margin: 0 0 0.5rem 0; }
pre { background: #f4f4f4; padding: 0.75rem; white-space: pre-wrap; }
table { border-collapse: collapse; }
th, td { border: 1px solid #999; padding: 0.25rem 0.75rem; text-align: left; }
</style>
</head>
<body>
<main>
<h1>Full axe report</h1>
<h2>Test details</h2>
<dl>
<dt>Page</dt><dd><code>${escape(results.url)}</code></dd>
<dt>Tested</dt><dd>${escape(new Date(results.timestamp).toLocaleString('en-AU'))}</dd>
<dt>axe version</dt><dd>${escape(results.testEngine?.version || 'Not recorded')}</dd>
<dt>Window size</dt><dd>${escape(env.windowWidth)} × ${escape(env.windowHeight)}</dd>
</dl>
<h2>Summary</h2>
<table>
<caption>Rule results</caption>
<tr><th scope="row">Violations</th><td>${results.violations.length}</td></tr>
<tr><th scope="row">Passes</th><td>${results.passes.length}</td></tr>
<tr><th scope="row">Needs review (incomplete)</th><td>${results.incomplete.length}</td></tr>
<tr><th scope="row">Not applicable</th><td>${results.inapplicable.length}</td></tr>
</table>
<p>Violations by impact: ${counts.critical} critical, ${counts.serious} serious, ${counts.moderate} moderate, ${counts.minor} minor.</p>
<h2>Violations</h2>
${sections}
<h2>Needs review</h2>
${review}
</main>
</body>
</html>`;
// 8. Save the report
fs.writeFileSync(path.join(__dirname, 'report-full.html'), report);
console.log('Full report saved to report-full.html');
In plain terms, the script:
- reads
results.json - sorts the violations from critical to minor, and counts them
- builds a section for each violation, with its details and failing elements
- builds a list of things that need a human to review
- wraps it all in an accessible HTML page and saves it as
report-full.html.
To check it worked, list the files in your folder (ls on Mac, dir on Windows). You should see make-report.js.
Watch out for:
- On Mac, the
codecommand only works once VS Code has been set up for it. If you see "command not found", open VS Code, press Command + Shift + P, typeshell command, and choose Install 'code' command in PATH. - Make sure the file is called exactly
make-report.js. If it shows up asmake-report.js.txt, rename it. - Any HTML in the results has to be escaped, which is what the
escapehelper does. Without it, code like<button></button>would show up in the report as a real button, not as text.
Run the report
Run the reporting script, then open the report in Chrome. Type each line separately, pressing Enter after each one.
Mac:
node make-report.js
open -a "Google Chrome" report-full.html
Windows:
node make-report.js
start chrome "%cd%\report-full.html"
After the first line, you should see Full report saved to report-full.html. The report shows the test details, a summary, then all 7 violations, sorted from critical to moderate.
Watch out for:
- If you see "ENOENT: no such file or directory ... results.json", the results file doesn't exist yet. Save the results first.
- The report only shows what's in the latest
results.json.
Add a shortcut command
Add a report shortcut to package.json, so one short command runs axe, builds the report and opens it.
Open package.json in your editor:
Mac:
code package.json
Windows:
notepad package.json
Find this part:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Replace it with the version for your computer, then save the file.
Mac:
"scripts": {
"report": "axe https://intopia.github.io/exercise/axe-core-test-page.html --save results.json --chromedriver-path node_modules/chromedriver/lib/chromedriver/chromedriver && node make-report.js && open -a \"Google Chrome\" report-full.html"
},
Windows:
"scripts": {
"report": "axe https://intopia.github.io/exercise/axe-core-test-page.html --save results.json --chromedriver-path node_modules/chromedriver/lib/chromedriver/chromedriver.exe && node make-report.js && start chrome \"%cd%\\report-full.html\""
},
In plain terms: "when I type npm run report, run axe on the test page and save the results, then build the report, then open it in Chrome."
&&means "then, if that worked, run this".- You don't need
npxinside a shortcut. npm already knows where to find the tools installed in your project.
Watch out for:
- JSON is very fussy. One missing comma or quote mark breaks the whole file.
- Quote marks inside a JSON value need a backslash in front, like
\". A single backslash is written as two, like\\. - On Windows, the ChromeDriver path uses forward slashes (
/) here. Windows is happy with them, and it avoids having to double every backslash.
Run the shortcut command
Type:
npm run report
You should see the results, then Full report saved to report-full.html, and then the report opens in Chrome.
Watch out for:
- Each run opens the report in a new Chrome tab. Close the old ones as you go.
- If you see
Missing script: "report", the change topackage.jsondidn't save, or the name is spelled differently.
Video walkthrough: Saving and reporting
Transcript
- Title: Saving and reporting
- Save the results to a file
- Explore the JSON file
- Write a reporting script
- Run the report
- Add a shortcut command
- Run the shortcut command
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
- Accessibility testing with Playwright