Repository: dequelabs/agnostic-axe
Branch: master
Commit: 04d7648329f9
Files: 10
Total size: 14.8 KB
Directory structure:
gitextract_jig8kg1k/
├── .gitignore
├── LICENSE
├── README.MD
├── WEBPACK_EXAMPLE.MD
├── __tests__/
│ └── index.mjs
├── package.json
└── src/
├── AuditQueue.mjs
├── AxeObserver.mjs
├── index.mjs
└── logViolations.mjs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# distribution
dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 LF Juliette Pretot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.MD
================================================
# agnostic-axe
Developer tool that continously observes the DOM to detect accessibility issues. Its audits are powered by [axe-core](https://github.com/dequelabs/axe-core).

## Basic Usage
This is all you need to start reporting accessibility issues to the browser console:
```js
import('https://unpkg.com/agnostic-axe@3').then(
({ AxeObserver, logViolations }) => {
const MyAxeObserver = new AxeObserver(logViolations)
MyAxeObserver.observe(document)
}
)
```
> To try agnostic-axe, paste the above code into the browser console on a site of your choosing.
When adding agnostic-axe to your project, be sure to only import it in your development environment. Else your application will use more resources than necessary. ([Here's an example of how to do this with webpack](WEBPACK_EXAMPLE.MD))
## API Details
### AxeObserver constructor
Accepts one parameter:
- `violationsCallback` (required). A function that is invoked with an array of violations, as reported by [axe-core](https://github.com/dequelabs/axe-core). To log violations to the console, simply pass the `logViolations` function exported by this module.
### AxeObserver.observe
Accepts one parameter:
- `targetNode` (required). A DOM node. AxeObserver audits this node, and continously monitors it for changes. If a change has been detected, AxeObserver audits the parts that have changed, and reports any new accessibility defects.
To observe multiple nodes, one can call the `AxeObserver.observe` method multiple times.
```js
MyAxeObserver.observe(document.getElementById('react-main'))
MyAxeObserver.observe(document.getElementById('vue-header'))
MyAxeObserver.observe(document.getElementById('page-footer'))
```
### AxeObserver.disconnect
Accepts no parameters.
Invoke this method to stop observing the DOM. This also clears the cache of violations that were already reported.
```js
MyAxeObserver.disconnect()
```
### Interacting with the axe-core API
The instance of axe-core used by agnostic-axe is exported by this module. Import it to interact with the [axe-core API](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md).
```js
import('https://unpkg.com/agnostic-axe@3').then(
({ axeCoreInstance, AxeObserver, logViolations }) => {
axeCoreInstance.registerPlugin(myPlugin)
// ...
}
)
```
## Comparison with react-axe
Unlike framework specific implementations of [axe-core](https://github.com/dequelabs/axe-core), such as [react-axe](https://github.com/dequelabs/react-axe), agnostic-axe uses a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to listen for changes directly in the DOM. This has two advantages:
1. It works with all web frameworks, and with any of their versions. This is key, as for example, at the time of writing, [react-axe](https://github.com/dequelabs/react-axe) does not work with the newer React features (function components and fragments), while agnostic-axe does supports them.
2. It only runs audits if the actual DOM changes. This means it uses less resources than [react-axe](https://github.com/dequelabs/react-axe), which runs audits when components rerender, even if their output does not change.
agnostic-axe is optimized for performance. Its audits are small chunks of work that run in the browser's idle periods.
================================================
FILE: WEBPACK_EXAMPLE.MD
================================================
# Webpack Example
```js
if (process.env.NODE_ENV !== 'production') {
// Import agnostic axe here.
// Webpack will comment out this code in production.
import('https://unpkg.com/agnostic-axe@3').then(
({ AxeObserver, logViolations }) => {
const MyAxeObserver = new AxeObserver(logViolations)
MyAxeObserver.observe(document)
}
)
}
```
================================================
FILE: __tests__/index.mjs
================================================
import Jasmine from 'jasmine'
import JasmineConsoleReporter from 'jasmine-console-reporter'
import * as AgnosticAxe from '../src/index.mjs'
const jasmine = new Jasmine()
jasmine.env.clearReporters()
jasmine.addReporter(
new JasmineConsoleReporter({
colors: true,
cleanStack: true,
verbosity: 4,
listStyle: 'indent',
activity: false
})
)
jasmine.env.describe('The AgnosticAxe module', () => {
jasmine.env.it('should export a `logViolations` function', () => {
expect(typeof AgnosticAxe.logViolations).toBe('function')
})
jasmine.env.it('should export a `AxeObserver` constructor', () => {
expect(typeof AgnosticAxe.AxeObserver).toBe('function')
})
})
jasmine.execute()
================================================
FILE: package.json
================================================
{
"name": "agnostic-axe",
"version": "3.0.3",
"description": "Framework agnostic accessibility auditing with axe-core",
"main": "dist/index.js",
"module": "dist/index.mjs",
"unpkg": "dist/standalone.mjs",
"source": "src/index.mjs",
"scripts": {
"build": "npm run build:dependencies && npm run build:standalone",
"build:dependencies": "npx microbundle -f es,cjs",
"build:standalone": "npx microbundle --external none -f es -o dist/standalone.js",
"dev": "npx microbundle --watch",
"prepublishOnly": "npm run build",
"test": "node --experimental-modules ./__tests__/index.mjs"
},
"repository": {
"type": "git",
"url": "git+https://laurajuliette@github.com/juliettepretot/agnostic-axe.git"
},
"keywords": [
"axe",
"accessibility",
"axe-core",
"audit",
"reporter"
],
"author": "LF Juliette Pretot",
"license": "MIT",
"bugs": {
"url": "https://github.com/juliettepretot/agnostic-axe/issues"
},
"homepage": "https://github.com/juliettepretot/agnostic-axe#readme",
"devDependencies": {
"clean-slate-lint": "^1.0.9",
"jasmine": "^3.5.0",
"jasmine-console-reporter": "^3.1.0",
"microbundle": "^0.11.0"
},
"husky": {
"hooks": {
"pre-commit": "./node_modules/.bin/clean-slate-lint",
"pre-rewrite": "./node_modules/.bin/clean-slate-lint"
}
},
"dependencies": {
"axe-core": "^3.5.1",
"idlize": "^0.1.1"
}
}
================================================
FILE: src/AuditQueue.mjs
================================================
import { rIC as requestIdleCallback } from 'idlize/idle-callback-polyfills.mjs'
export default class AuditQueue {
constructor() {
this._pendingAudits = new Map()
this._isRunning = false
this.run = this.run.bind(this)
this._scheduleAudits = this._scheduleAudits.bind(this)
}
run(node, getAuditResult) {
if (this._pendingAudits.has(node)) {
// This node is already scheduled to be audited.
return null
}
// Returns a promise that resolves when this node is audited.
return new Promise((resolve, reject) => {
const runAudit = async () => {
try {
const result = await getAuditResult()
resolve(await result)
} catch (error) {
reject(error)
}
}
this._pendingAudits.set(node, runAudit)
if (!this._isRunning) this._scheduleAudits()
})
}
_scheduleAudits() {
this._isRunning = true
requestIdleCallback(async IdleDeadline => {
const iterator = this._pendingAudits.entries()
for (const [node, runAudit] of iterator) {
// Only run one audit at a time, as axe-core does not allow for
// concurrent runs.
// Ref: https://github.com/dequelabs/axe-core/issues/1041
await runAudit()
this._pendingAudits.delete(node)
if (IdleDeadline.timeRemaining() === 0) {
break
}
}
if (this._pendingAudits.size > 0) {
// If pending audits remain, schedule them for the next idle phase.
this._scheduleAudits()
} else {
// The queue is empty, we're no longer running
this._isRunning = false
}
})
}
}
================================================
FILE: src/AxeObserver.mjs
================================================
import axeCore from 'axe-core'
import AuditQueue from './AuditQueue.mjs'
// Apply default axeCore config
axeCore.configure({
reporter: 'v2',
checks: [
{
id: 'color-contrast',
options: {
// Prevent axe from automatically scrolling
noScroll: true
}
}
]
})
export const axeCoreInstance = axeCore
// Axe core does not allow parallel audits in the same env. Hence a queue is shared
// across AxeObserver instances.
const SharedAuditQueue = new AuditQueue()
// The AxeObserver class takes a violationsCallback, which is invoked with an
// array of observed violations.
export default class AxeObserver {
constructor(violationsCallback) {
if (typeof violationsCallback !== 'function') {
throw new Error(
'The AxeObserver constructor requires a violationsCallback'
)
}
this._violationsCallback = violationsCallback
this.observe = this.observe.bind(this)
this.disconnect = this.disconnect.bind(this)
this._alreadyReportedIncidents = new Set()
this._mutationObserver = new window.MutationObserver(mutationRecords => {
mutationRecords.forEach(mutationRecord => {
this._auditNode(mutationRecord.target)
})
})
}
observe(targetNode) {
if (!targetNode) {
throw new Error('AxeObserver.observe requires a targetNode')
}
this._mutationObserver.observe(targetNode, {
attributes: true,
subtree: true
})
// run initial audit on the whole targetNode
this._auditNode(targetNode)
}
disconnect() {
this._mutationObserver.disconnect()
this._alreadyReportedIncidents.clear()
}
async _auditNode(node) {
const response = await SharedAuditQueue.run(node, async () => {
// Since audits are scheduled asynchronously, it can happen that
// the node is no longer connected. We cannot analyze it then.
return node.isConnected ? axeCore.run(node) : null
})
if (!response) return
const violationsToReport = response.violations.filter(violation => {
const filteredNodes = violation.nodes.filter(node => {
const key = node.target.toString() + violation.id
const wasAlreadyReported = this._alreadyReportedIncidents.has(key)
if (wasAlreadyReported) {
// filter out this violation for this node
return false
} else {
// add to alreadyReportedIncidents as we'll report it now
this._alreadyReportedIncidents.add(key)
return true
}
})
return filteredNodes.length > 0
})
const hasViolationsToReport = violationsToReport.length > 0
if (hasViolationsToReport) {
this._violationsCallback(violationsToReport)
}
}
}
================================================
FILE: src/index.mjs
================================================
import _AxeObserver, {
axeCoreInstance as _axeCoreInstance
} from './AxeObserver.mjs'
import _logViolations from './logViolations.mjs'
export const AxeObserver = _AxeObserver
export const axeCoreInstance = _axeCoreInstance
export const logViolations = _logViolations
================================================
FILE: src/logViolations.mjs
================================================
import { axeCoreInstance } from './AxeObserver'
const boldCourier = 'font-weight:bold;font-family:Courier;'
const critical = 'color:red;font-weight:bold;'
const serious = 'color:red;font-weight:normal;'
const moderate = 'color:orange;font-weight:bold;'
const minor = 'color:orange;font-weight:normal;'
const defaultReset = 'font-color:black;font-weight:normal;'
// The logViolations function takes an array of violations and logs them to the
// console in a nice format. Its code is copied from the `react-axe` module.
// Ref: https://github.com/dequelabs/react-axe
export default function logViolations(violations) {
if (violations.length) {
console.group('%cNew aXe issues', serious)
violations.forEach(function(result) {
var fmt
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(function(node) {
failureSummary(node, 'any')
failureSummary(node, 'none')
})
console.groupEnd()
})
console.groupEnd()
}
}
function logElement(node, logFn) {
var el = document.querySelector(node.target.toString())
if (!el) {
logFn('Selector: %c%s', boldCourier, node.target.toString())
} else {
logFn('Element: %o', el)
}
}
function logHtml(node) {
console.log('HTML: %c%s', boldCourier, node.html)
}
function logFailureMessage(node, key) {
var message = axeCoreInstance._audit.data.failureSummaries[
key
].failureMessage(
node[key].map(function(check) {
return check.message || ''
})
)
console.error(message)
}
function failureSummary(node, key) {
if (node[key].length > 0) {
logElement(node, console.groupCollapsed)
logHtml(node)
logFailureMessage(node, key)
var relatedNodes = []
node[key].forEach(function(check) {
relatedNodes = relatedNodes.concat(check.relatedNodes)
})
if (relatedNodes.length > 0) {
console.groupCollapsed('Related nodes')
relatedNodes.forEach(function(relatedNode) {
logElement(relatedNode, console.log)
logHtml(relatedNode)
})
console.groupEnd()
}
console.groupEnd()
}
}
gitextract_jig8kg1k/
├── .gitignore
├── LICENSE
├── README.MD
├── WEBPACK_EXAMPLE.MD
├── __tests__/
│ └── index.mjs
├── package.json
└── src/
├── AuditQueue.mjs
├── AxeObserver.mjs
├── index.mjs
└── logViolations.mjs
SYMBOL INDEX (14 symbols across 3 files)
FILE: src/AuditQueue.mjs
class AuditQueue (line 3) | class AuditQueue {
method constructor (line 4) | constructor() {
method run (line 11) | run(node, getAuditResult) {
method _scheduleAudits (line 32) | _scheduleAudits() {
FILE: src/AxeObserver.mjs
class AxeObserver (line 26) | class AxeObserver {
method constructor (line 27) | constructor(violationsCallback) {
method observe (line 46) | observe(targetNode) {
method disconnect (line 59) | disconnect() {
method _auditNode (line 63) | async _auditNode(node) {
FILE: src/logViolations.mjs
function logViolations (line 13) | function logViolations(violations) {
function logElement (line 53) | function logElement(node, logFn) {
function logHtml (line 62) | function logHtml(node) {
function logFailureMessage (line 66) | function logFailureMessage(node, key) {
function failureSummary (line 78) | function failureSummary(node, key) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (16K chars).
[
{
"path": ".gitignore",
"chars": 935,
"preview": "# distribution\ndist\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "MIT License\n\nCopyright (c) 2019 LF Juliette Pretot\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.MD",
"chars": 3426,
"preview": "# agnostic-axe\n\nDeveloper tool that continously observes the DOM to detect accessibility issues. Its audits are powered "
},
{
"path": "WEBPACK_EXAMPLE.MD",
"chars": 362,
"preview": "# Webpack Example\n\n```js\nif (process.env.NODE_ENV !== 'production') {\n // Import agnostic axe here.\n // Webpack will c"
},
{
"path": "__tests__/index.mjs",
"chars": 713,
"preview": "import Jasmine from 'jasmine'\nimport JasmineConsoleReporter from 'jasmine-console-reporter'\nimport * as AgnosticAxe from"
},
{
"path": "package.json",
"chars": 1442,
"preview": "{\n \"name\": \"agnostic-axe\",\n \"version\": \"3.0.3\",\n \"description\": \"Framework agnostic accessibility auditing with axe-c"
},
{
"path": "src/AuditQueue.mjs",
"chars": 1657,
"preview": "import { rIC as requestIdleCallback } from 'idlize/idle-callback-polyfills.mjs'\n\nexport default class AuditQueue {\n con"
},
{
"path": "src/AxeObserver.mjs",
"chars": 2731,
"preview": "import axeCore from 'axe-core'\nimport AuditQueue from './AuditQueue.mjs'\n\n// Apply default axeCore config\naxeCore.config"
},
{
"path": "src/index.mjs",
"chars": 270,
"preview": "import _AxeObserver, {\n axeCoreInstance as _axeCoreInstance\n} from './AxeObserver.mjs'\nimport _logViolations from './lo"
},
{
"path": "src/logViolations.mjs",
"chars": 2565,
"preview": "import { axeCoreInstance } from './AxeObserver'\n\nconst boldCourier = 'font-weight:bold;font-family:Courier;'\nconst criti"
}
]
About this extraction
This page contains the full source code of the dequelabs/agnostic-axe GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (14.8 KB), approximately 3.9k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.