The getAxeViolations extension runs axe-core accessibility checks against the current page and returns a structured summary of accessibility violations.
This is useful when a journey needs to audit a full page or a scoped page area for WCAG-related accessibility issues and assert on violation counts, impact counts, incomplete checks, or axe engine details.
Parameters:
-
tagsoptional, a comma-separated string or array of axe tags to run. If omitted, the script useswcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa,best-practice; -
includeoptional, a CSS selector string used to scope the audit to a specific page area such as#mainor#app; -
excludeoptional, a CSS selector string used to exclude a page area such as a third-party widget, cookie banner, or footer; -
minImpactoptional, a string used as the minimum impact filter. Supported values areminor,moderate,serious, andcritical; -
summaryOnlyoptional, passtrueor"true"to omit per-node DOM details from the returned payload;
Note: The extension requires axe-core to be loaded from the configured resource before axe.run can execute.
NLP usage
Use the extension in a journey by calling getAxeViolations. Pass each value to the matching extension input using as inputName.
Note: The extension returns the result through done(JSON.stringify(...)), so the output can be stored and asserted against in later journey steps.
getAxeViolations returning $a11y
assert ${ $a11y.violationCount } equals "0"getAxeViolations using "wcag2aa,wcag21aa,wcag22aa" as tags, "#app" as include, ".third-party-widget" as exclude, "critical" as minImpact, "true" as summaryOnly returning $a11y
assert ${ $a11y.violationCount } equals "0"You can also pass stored variables into the extension inputs.
getAxeViolations using "$axeTags" as tags, "$auditScope" as include, "$skipSelector" as exclude, "$impactFloor" as minImpact, "$summaryMode" as summaryOnly returning $a11ystore value "wcag2aa,wcag21aa" in $axeTags
store value "#main" in $auditScope
store value "#cookie-banner" in $skipSelector
store value "serious" in $impactFloor
store value "true" in $summaryMode
getAxeViolations using "$axeTags" as tags, "$auditScope" as include, "$skipSelector" as exclude, "$impactFloor" as minImpact, "$summaryMode" as summaryOnly returning $a11yExample output when no violations are returned:
{
"url": "https://example.com/",
"timestamp": "2026-05-27T10:30:00.000Z",
"testEngine": {
"name": "axe-core",
"version": "4.11.0"
},
"violationCount": 0,
"incompleteCount": 0,
"impactCounts": {
"critical": 0,
"serious": 0,
"moderate": 0,
"minor": 0
},
"violations": []
}This extension requires the following resources:
The extension should be configured as:
- Run asynchronously: Yes
- Scope: Global
Limitation: The extension depends on axe-core loading successfully from the configured CDN resource and on the browser DOM available at execution time. Scoped audits use CSS selectors for include and exclude, so invalid or unavailable selectors can affect results. The script filters violations by impact after axe returns results, uses a 90-second timeout, and can omit node details only when summaryOnly is true. It reports axe findings but does not automatically fix accessibility issues or replace manual accessibility review for incomplete results.
Add the extension to your Virtuoso instance
Select the domain that matches your Virtuoso account.
View source
Last updated: 25/05/2026
Resources:
/**
*
* // Simplest — full page, all WCAG violations
* getAxeViolations returning $a11y
* assert ${ $a11y.violationCount } equals "0"
*
* // Only critical/serious, skip a noisy widget
* getAxeViolations("wcag2aa,wcag21aa", "#app", ".intercom-launcher", "serious") returning $a11y
* assert ${ $a11y.impactCounts.critical } equals "0"
* assert ${ $a11y.impactCounts.serious } equals "0"
*
* // Fast check — no per-node DOM detail in payload
* getAxeViolations("wcag2aa", "", "", "", "true") returning $a11y
* assert ${ $a11y.violationCount } equals "0"
*/
// Optional positional params:
// tags comma-separated axe tags. Default: "wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa"
// include CSS selector to scope the audit, e.g. "#main"
// exclude CSS selector to skip, e.g. ".third-party-widget"
// minImpact "minor" | "moderate" | "serious" | "critical" — filter floor
// summaryOnly "true" to omit per-node DOM detail (smaller payload)
//
// Returns: { url, timestamp, testEngine, violationCount, incompleteCount,
// impactCounts:{critical,serious,moderate,minor}, violations:[...] }
const DEFAULT_TAGS = ["wcag2a","wcag2aa","wcag21a","wcag21aa","wcag22aa","best-practice"];
const IMPACT_RANK = { minor:1, moderate:2, serious:3, critical:4 };
const RUN_TIMEOUT_MS = 90000; // Virtuoso's 120s extension cap, script timeout limit
const optTags = (typeof tags !== "undefined") ? tags : null;
const optInclude = (typeof include !== "undefined" && include !== "") ? include : null;
const optExclude = (typeof exclude !== "undefined" && exclude !== "") ? exclude : null;
const optMinImpact = (typeof minImpact !== "undefined" && minImpact)
? String(minImpact).toLowerCase() : null;
const optSummaryOnly = (typeof summaryOnly !== "undefined")
&& (summaryOnly === true || summaryOnly === "true");
if (typeof axe === "undefined" || typeof axe.run !== "function") {
doneError("axe-core not loaded. Check the Resources URL on this extension.");
} else {
const parseTags = t =>
!t ? DEFAULT_TAGS
: Array.isArray(t) ? t.filter(Boolean)
: String(t).split(",").map(s => s.trim()).filter(Boolean);
const buildContext = () => {
if (!optInclude && !optExclude) return document;
const ctx = {};
if (optInclude) ctx.include = [optInclude];
if (optExclude) ctx.exclude = [optExclude];
return ctx;
};
const filterByImpact = vs => {
if (!optMinImpact || !(optMinImpact in IMPACT_RANK)) return vs;
const floor = IMPACT_RANK[optMinImpact];
return vs.filter(v => IMPACT_RANK[v.impact || "minor"] >= floor);
};
const compactNodes = nodes => (nodes || []).map(n => ({
target: n.target,
html: (n.html || "").slice(0, 500),
failureSummary: n.failureSummary
}));
const buildResult = results => {
const filtered = filterByImpact(results.violations || []);
const impactCounts = { critical:0, serious:0, moderate:0, minor:0 };
filtered.forEach(v => {
const k = v.impact || "minor";
if (impactCounts[k] !== undefined) impactCounts[k]++;
});
return {
url: results.url,
timestamp: results.timestamp,
testEngine: results.testEngine,
violationCount: filtered.length,
incompleteCount: (results.incomplete || []).length,
impactCounts,
violations: filtered.map(v => {
const o = {
id: v.id,
impact: v.impact,
help: v.help,
helpUrl: v.helpUrl,
description: v.description,
tags: v.tags,
nodeCount: (v.nodes || []).length
};
if (!optSummaryOnly) o.nodes = compactNodes(v.nodes);
return o;
})
};
};
const runOptions = {
resultTypes: ["violations", "incomplete"],
runOnly: { type: "tag", values: parseTags(optTags) }
};
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error(`axe.run timed out after ${RUN_TIMEOUT_MS}ms`)),
RUN_TIMEOUT_MS
);
});
Promise.race([axe.run(buildContext(), runOptions), timeoutPromise])
.then(results => {
clearTimeout(timeoutId);
done(JSON.stringify(buildResult(results)));
})
.catch(err => {
clearTimeout(timeoutId);
doneError("getAxeViolations failed: " +
(err && err.message ? err.message : String(err)));
});
}
Comments
0 comments
Please sign in to leave a comment.