-
Notifications
You must be signed in to change notification settings - Fork 16
Description
User story
As a Code PushUp user, I want to track accessibility compliance using axe-core so that I can ensure my web applications meet WCAG 2.1 AA and EAA requirements.
Acceptance criteria
Plugin configuration
- The plugin function
axePlugin()accepts a required config parameter specifying the URL(s) to analyze in three formats:- string (
'https://example.com') - array (
['https://example.com', 'https://another-example.com']) - weighted object (
{ 'https://example.com': 2, 'https://another-example.com': 1 })
- string (
- The plugin function accepts an optional second parameter
optionsobject that contains:- A
presetfield (string) that determines which rules to run (default:'wcag21aa') - A
scoreTargetsfield for defining custom pass/fail thresholds (optional)
- A
- All configuration inputs are validated using Zod schemas:
- The
configschema validates URL format(s) - The
optionsschema validatespresetvalues andscoreTargetsstructure
- The
Note
scoreTargets has limited practical value for this plugin since all audits use binary scoring (0 or 1), but it's included for consistency with other plugins and for potential future use in category-level scoring.
const axePluginConfigSchema = z.union([
z.url(),
z.array(z.url()),
z.record(z.url(), z.number()),
]);
const axePluginOptionsSchema = z.object({
preset: z
.enum(['wcag21aa', 'wcag22aa', 'best-practice', 'all'])
.default('wcag21aa'),
scoreTargets: pluginScoreTargetsSchema.optional(),
});axe-core integration
- The plugin uses two packages from the axe ecosystem:
- Both packages are installed as direct dependencies
- In the plugin factory, the plugin calls
axe.getRules()fromaxe-coreto load all available rules and filter them based on the selected preset - In the runner function, the plugin launches a headless Chromium browser using playwright-core (a peer dependency)
- For each configured URL, the runner navigates to the page and executes
new AxeBuilder({ page }).withTags(...).analyze()from@axe-core/playwrightto run the accessibility scan - The runner collects all results from all URLs before closing the browser
- The
Loggeris used throughout execution to log info when starting, debug for each URL tested, errors if browser or axe fails, and warnings if axe returns incomplete results
Audit generation
- The plugin loads all axe rules using
axe.getRules()API and filters them based on the selected preset:- For WCAG presets: rules are filtered by WCAG level tags
- For best-practice: rules are filtered by best-practice tag
- For all: all rules are included
- Each axe rule is transformed to a Code PushUp audit where:
-
rule.ruleIdmaps toaudit.slug -
rule.helpmaps toaudit.title -
rule.descriptionmaps toaudit.description -
rule.helpUrlmaps toaudit.docsUrl
-
Result transformation
- The plugin transforms axe execution results (containing violations, passes, incomplete, and inapplicable arrays) into
AuditOutputswhere each rule produces anAuditOutputwith:- The
slugset to the rule ID - A binary
scoreof 1 when no violations exist, or 0 when any violations exist - The
valueset to the count of violation nodes - A
displayValueformatted as human-readable text (e.g., "3 violations")
- The
- Violations are transformed into issues where:
- Each violation node becomes an
Issueobject with the message extracted from the nodefailureSummaryorrule.help - Issue messages include the page URL and CSS selector from the node
targetor snippet from the nodehtmlfor context - Axe impact levels map to Code PushUp severity:
- critical/serious ➡️ error
- moderate ➡️ warning
- minor ➡️ info
- Source location is not used
- Each violation node becomes an
Group generation
- When using WCAG presets (
wcag21aa,wcag22aa), the plugin generates groups including all audits tagged with the corresponding WCAG level, and all refs have weight 1:wcag21aa➡️ tags: [wcag2a, 'wcag21a','wcag2aa,wcag21aa] (WCAG 2.1 AA compliance)wcag22aa➡️ tags: [wcag2a, 'wcag21a','wcag2aa,wcag21aa,wcag22aa] (WCAG 2.2 AA compliance)
- When using the
best-practicepreset, the plugin extracts unique category tags (cat.*) from rules and creates a group for each category:cat.aria➡️ ARIAcat.color➡️ Color & Contrastcat.forms➡️ Formscat.keyboard➡️ Keyboardcat.language➡️ Languagecat.name-role-value➡️ Names & Labelscat.parsing➡️ Parsingcat.semantics➡️ Semanticscat.sensory-and-visual-cues➡️ Visual Cuescat.structure➡️ Structurecat.tables➡️ Tablescat.text-alternatives➡️ Text Alternativescat.time-and-media➡️ Media
- When using the
allpreset, the plugin combines WCAG groups with key category groups - Before returning the plugin configuration, empty groups (those with
refs.length === 0) are filtered out to prevent validation errors
Plugin metadata
- The plugin includes metadata:
slugaxetitleAxe Accessibilityiconaccessibilitydescriptionexplaining EAA/WCAG compliancedocsUrllinking to the package documentation on npmjs.compackageNameandversionfrom package.json
Unit tests
- Configuration schema validation tests verify that valid URL formats are accepted, invalid URLs are rejected, preset values are validated, and scoreTargets are validated correctly
- Preset logic tests verify that each preset returns the correct rule count, and rule filtering works correctly
- Transformation function tests verify axe violations map to
Issuescorrectly, impact maps to severity correctly, score calculation produces binary 0 or 1, anddisplayValueis formatted properly - Group generation tests verify that correct groups are generated for each preset, empty groups are filtered out, and group refs include correct audit slugs
E2E tests
- A test fixture exists containing an HTML file with known accessibility violations (missing alt text, poor color contrast, invalid ARIA attributes, missing form labels)
- A
code-pushup.config.tsfile uses the axe plugin with default options - When the collect command runs, it generates
report.jsonthat validates against the schema and contains expected violations with specific audits (image-alt, color-contrast, aria-allowed-attr) showing score: 0 - The generated report contains the expected groups (
wcag21-level-aandwcag21-level-aa) with correct audit refs
Documentation
- The
README.mdcontains installation instructions, a quick start example with zero configuration, an explanation of each preset option,scoreTargetsusage examples, an API reference section, and links to axe-core rules documentation, WCAG guidelines, and EAA compliance information - Where necessary, exported functions include TSDoc comments
- The package is configured for publishing to npm as
@code-pushup/axe-pluginwith correct package.json metadata, peer dependencies, and build configuration
Implementation details
Follow the Lighthouse plugin pattern:
- Direct library integration
RunnerFunctionwith programmatic axe-core execution- Output transformation pipeline
- Auto-generate groups from rule metadata
Basic file structure
packages/plugin-axe/
├── src/
│ ├── lib/
│ │ ├── axe-plugin.ts Main plugin function
│ │ ├── config.ts Zod schemas
│ │ ├── constants.ts Plugin metadata
│ │ ├── processing.ts
│ │ ├── runner/
│ │ │ ├── runner.ts Runner function
│ │ │ ├── run-axe.ts Axe-core execution
│ │ │ └── transform.ts Result transformation
│ │ └── meta/
│ │ └── transform.ts Helpers loading and transforming Axe rules
│ │
│ └── index.ts
├── README.md
├── package.json
└── vitest.unit.config.ts
e2e/plugin-axe-e2e/
├── tests/
│ └── collect.e2e.test.ts
├── mocks/
│ └── fixtures/
│ └── default-setup/
│ ├── code-pushup.config.ts
│ └── test-app.html
└── vitest.e2e.config.tsRelated resources
matejchalk
Metadata
Metadata
Assignees
Labels
No labels