@code-pushup/axe-plugin 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # @code-pushup/plugin-axe
2
+
3
+ 🕵️ **Code PushUp plugin for automated accessibility testing with Axe.** 🌐
4
+
5
+ ---
6
+
7
+ The plugin runs accessibility audits on web pages using [axe-core](https://github.com/dequelabs/axe-core) via Playwright. It identifies [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) violations and best practice recommendations, providing **100+ accessibility metrics** with detailed guidance for fixing problems.
8
+
9
+ **Why accessibility testing matters:** The [European Accessibility Act (EAA)](https://ec.europa.eu/social/main.jsp?catId=1202) requires digital products and services in the EU to meet accessibility standards closely aligned with the Web Content Accessibility Guidelines (WCAG). This plugin provides comprehensive automated testing to help meet compliance requirements and build inclusive experiences.
10
+
11
+ ## Getting started
12
+
13
+ 1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file.
14
+
15
+ 2. Install as a dev dependency with your package manager:
16
+
17
+ ```sh
18
+ npm install --save-dev @code-pushup/plugin-axe
19
+ ```
20
+
21
+ ```sh
22
+ yarn add --dev @code-pushup/plugin-axe
23
+ ```
24
+
25
+ ```sh
26
+ pnpm add --save-dev @code-pushup/plugin-axe
27
+ ```
28
+
29
+ 3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.ts`).
30
+
31
+ Pass in the URL you want to test:
32
+
33
+ ```ts
34
+ import axePlugin from '@code-pushup/plugin-axe';
35
+
36
+ export default {
37
+ // ...
38
+ plugins: [
39
+ // ...
40
+ axePlugin('https://example.com'),
41
+ ],
42
+ };
43
+ ```
44
+
45
+ By default, the plugin runs **WCAG 2.1 Level AA** audits. You can customize this using [presets](#presets).
46
+
47
+ 4. Run the CLI with `npx code-pushup collect` and view or upload the report (refer to [CLI docs](../cli/README.md)).
48
+
49
+ > **Note:** The plugin uses Playwright to run tests in Chromium. The browser will be installed automatically if not already present.
50
+
51
+ ## Configuration
52
+
53
+ ```ts
54
+ axePlugin(urls: PluginUrls, options?: AxePluginOptions)
55
+ ```
56
+
57
+ **Parameters:**
58
+
59
+ - `urls` - URL(s) to test. See [Multiple URLs](#multiple-urls) section below, or [`PluginUrls`](../../packages/models/docs/models-reference.md#pluginurls) reference
60
+ - `options` - Optional configuration object
61
+
62
+ **Options:**
63
+
64
+ | Property | Type | Default | Description |
65
+ | -------------- | ----------- | ------------ | ----------------------------------------- |
66
+ | `preset` | `AxePreset` | `'wcag21aa'` | Accessibility ruleset preset |
67
+ | `scoreTargets` | `object` | `undefined` | Pass/fail thresholds for audits or groups |
68
+
69
+ See [Presets](#presets) for the list of available presets and [Preset details](#preset-details) for what each preset includes.
70
+
71
+ ## Multiple URLs
72
+
73
+ The plugin supports testing single or multiple URLs:
74
+
75
+ ```ts
76
+ // Single URL (string)
77
+ axePlugin('https://example.com');
78
+
79
+ // Multiple URLs (array)
80
+ axePlugin(['https://example.com', 'https://example.com/about']);
81
+
82
+ // Weighted URLs (record)
83
+ axePlugin({
84
+ 'https://example.com': 3, // homepage has 3x weight
85
+ 'https://example.com/about': 1, // about page has 1x weight
86
+ });
87
+ ```
88
+
89
+ URLs with higher weights contribute more to overall scores. For example, a URL with weight 3 has three times the influence of a URL with weight 1.
90
+
91
+ ## Presets
92
+
93
+ Choose which accessibility ruleset to test against using the `preset` option:
94
+
95
+ ```ts
96
+ axePlugin('https://example.com', {
97
+ preset: 'wcag22aa',
98
+ });
99
+ ```
100
+
101
+ Available presets:
102
+
103
+ | Preset | Description | Use case |
104
+ | --------------- | --------------------------------------------- | ------------------------------------- |
105
+ | `wcag21aa` | **WCAG 2.1 Level A and AA** (default) | Standard web accessibility compliance |
106
+ | `wcag22aa` | **WCAG 2.2 Level A and AA** | Latest WCAG standard |
107
+ | `best-practice` | **Best practices** (non-WCAG recommendations) | Deque's additional recommendations |
108
+ | `all` | **All available rules** | Comprehensive testing |
109
+
110
+ ### Preset details
111
+
112
+ **`wcag21aa`** (default)
113
+
114
+ - Tests compliance with [WCAG 2.1 Level A and Level AA](https://www.w3.org/WAI/WCAG21/quickref/?currentsidebar=%23col_overview&levels=a%2Caaa)
115
+ - Required for [European Accessibility Act (EAA)](https://ec.europa.eu/social/main.jsp?catId=1202) compliance
116
+ - Covers fundamentals: keyboard navigation, color contrast, form labels, alt text, etc.
117
+
118
+ **`wcag22aa`**
119
+
120
+ - Tests compliance with [WCAG 2.2 Level A and Level AA](https://www.w3.org/WAI/WCAG22/quickref/?currentsidebar=%23col_overview&levels=a%2Caaa)
121
+ - Includes all WCAG 2.1 rules plus new success criteria
122
+ - Future-proof for upcoming regulations
123
+
124
+ **`best-practice`**
125
+
126
+ - Non-WCAG recommendations from Deque Systems
127
+ - Goes beyond legal requirements
128
+ - Helps create more inclusive experiences
129
+
130
+ **`all`**
131
+
132
+ - Runs every rule available in axe-core
133
+ - Includes WCAG 2.0, 2.1, 2.2, experimental rules, and best practices
134
+ - Most comprehensive testing
135
+
136
+ > **Note:** Different pages may trigger different sets of rules depending on their content. For example, a page without video won't run video-related audits.
137
+
138
+ ### Groups
139
+
140
+ The plugin automatically creates groups to organize audits:
141
+
142
+ **WCAG presets** (`wcag21aa`, `wcag22aa`):
143
+
144
+ - `wcag21-level-a` - WCAG 2.1 Level A audits
145
+ - `wcag21-level-aa` - WCAG 2.1 Level AA audits
146
+ - `wcag22-level-a` - WCAG 2.2 Level A audits (wcag22aa only)
147
+ - `wcag22-level-aa` - WCAG 2.2 Level AA audits (wcag22aa only)
148
+
149
+ **Best practice preset** (`best-practice`):
150
+
151
+ - `aria` - ARIA
152
+ - `color` - Color & Contrast
153
+ - `forms` - Forms
154
+ - `keyboard` - Keyboard
155
+ - `language` - Language
156
+ - `name-role-value` - Names & Labels
157
+ - `parsing` - Parsing
158
+ - `semantics` - Semantics
159
+ - `sensory-and-visual-cues` - Visual Cues
160
+ - `structure` - Structure
161
+ - `tables` - Tables
162
+ - `text-alternatives` - Text Alternatives
163
+ - `time-and-media` - Media
164
+
165
+ **All preset** (`all`):
166
+
167
+ - Combines all WCAG groups and best practice category groups
168
+
169
+ Use `npx code-pushup print-config --onlyPlugins=axe` to list all audits and groups for your configuration.
170
+
171
+ ## Resources
172
+
173
+ - **[Axe-core rules](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md)** - Complete list of accessibility rules
174
+ - **[Deque University](https://dequeuniversity.com/rules/axe/)** - Detailed explanations and remediation guidance
175
+ - **[WCAG Guidelines](https://www.w3.org/WAI/standards-guidelines/wcag/)** - Web Content Accessibility Guidelines
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@code-pushup/axe-plugin",
3
+ "version": "0.87.0",
4
+ "license": "MIT",
5
+ "description": "Code PushUp plugin for detecting accessibility issues using Axe 🌐",
6
+ "homepage": "https://github.com/code-pushup/cli/tree/main/packages/plugin-axe#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A%22🧩%20axe-plugin%22"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/code-pushup/cli.git",
13
+ "directory": "packages/plugin-axe"
14
+ },
15
+ "keywords": [
16
+ "CLI",
17
+ "Code PushUp",
18
+ "plugin",
19
+ "axe",
20
+ "axe-core",
21
+ "accessibility",
22
+ "a11y",
23
+ "WCAG",
24
+ "compliance",
25
+ "testing",
26
+ "quality",
27
+ "automation",
28
+ "runtime analysis",
29
+ "audit",
30
+ "score monitoring",
31
+ "developer tools",
32
+ "conformance",
33
+ "KPI tracking",
34
+ "tech debt",
35
+ "automated feedback",
36
+ "regression guard",
37
+ "actionable feedback"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "type": "module",
43
+ "dependencies": {
44
+ "@axe-core/playwright": "^4.11.0",
45
+ "@code-pushup/models": "0.87.0",
46
+ "@code-pushup/utils": "0.87.0",
47
+ "axe-core": "^4.11.0",
48
+ "playwright-core": "^1.56.1",
49
+ "zod": "^4.1.12"
50
+ },
51
+ "files": [
52
+ "src",
53
+ "!**/*.tsbuildinfo"
54
+ ],
55
+ "types": "./src/index.d.ts",
56
+ "module": "./src/index.js",
57
+ "main": "./src/index.js"
58
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { axePlugin } from './lib/axe-plugin.js';
2
+ export default axePlugin;
3
+ export type { AxePluginOptions } from './lib/config.js';
4
+ export type { AxePreset } from './lib/constants.js';
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { axePlugin } from './lib/axe-plugin.js';
2
+ export default axePlugin;
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,eAAe,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type PluginConfig, type PluginUrls } from '@code-pushup/models';
2
+ import { type AxePluginOptions } from './config.js';
3
+ /**
4
+ * Code PushUp plugin for accessibility testing using axe-core.
5
+ *
6
+ * @public
7
+ * @param urls - {@link PluginUrls} URL(s) to test
8
+ * @param options - {@link AxePluginOptions} Plugin options
9
+ * @returns Plugin configuration
10
+ */
11
+ export declare function axePlugin(urls: PluginUrls, options?: AxePluginOptions): PluginConfig;
@@ -0,0 +1,36 @@
1
+ import { createRequire } from 'node:module';
2
+ import { validate, } from '@code-pushup/models';
3
+ import { normalizeUrlInput } from '@code-pushup/utils';
4
+ import { axePluginOptionsSchema } from './config.js';
5
+ import { AXE_PLUGIN_SLUG } from './constants.js';
6
+ import { processAuditsAndGroups } from './processing.js';
7
+ import { createRunnerFunction } from './runner/runner.js';
8
+ /**
9
+ * Code PushUp plugin for accessibility testing using axe-core.
10
+ *
11
+ * @public
12
+ * @param urls - {@link PluginUrls} URL(s) to test
13
+ * @param options - {@link AxePluginOptions} Plugin options
14
+ * @returns Plugin configuration
15
+ */
16
+ export function axePlugin(urls, options = {}) {
17
+ const { preset, scoreTargets, timeout } = validate(axePluginOptionsSchema, options);
18
+ const { urls: normalizedUrls, context } = normalizeUrlInput(urls);
19
+ const { audits, groups, ruleIds } = processAuditsAndGroups(normalizedUrls, preset);
20
+ const packageJson = createRequire(import.meta.url)('../../package.json');
21
+ return {
22
+ slug: AXE_PLUGIN_SLUG,
23
+ packageName: packageJson.name,
24
+ version: packageJson.version,
25
+ title: 'Axe Accessibility',
26
+ icon: 'folder-syntax',
27
+ description: 'Official Code PushUp Axe plugin for automated accessibility testing',
28
+ docsUrl: 'https://www.npmjs.com/package/@code-pushup/axe-plugin',
29
+ audits,
30
+ groups,
31
+ runner: createRunnerFunction(normalizedUrls, ruleIds, timeout),
32
+ context,
33
+ ...(scoreTargets && { scoreTargets }),
34
+ };
35
+ }
36
+ //# sourceMappingURL=axe-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axe-plugin.js","sourceRoot":"","sources":["../../../src/lib/axe-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAGL,QAAQ,GACT,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAyB,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CACvB,IAAgB,EAChB,UAA4B,EAAE;IAE9B,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,QAAQ,CAChD,sBAAsB,EACtB,OAAO,CACR,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAElE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,sBAAsB,CACxD,cAAc,EACd,MAAM,CACP,CAAC;IAEF,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAChD,oBAAoB,CACkB,CAAC;IAEzC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,WAAW,CAAC,IAAI;QAC7B,OAAO,EAAE,WAAW,CAAC,OAAO;QAC5B,KAAK,EAAE,mBAAmB;QAC1B,IAAI,EAAE,eAAe;QACrB,WAAW,EACT,qEAAqE;QACvE,OAAO,EAAE,uDAAuD;QAChE,MAAM;QACN,MAAM;QACN,MAAM,EAAE,oBAAoB,CAAC,cAAc,EAAE,OAAO,EAAE,OAAO,CAAC;QAC9D,OAAO;QACP,GAAG,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,CAAC;KACtC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ export declare const axePluginOptionsSchema: z.ZodObject<{
3
+ preset: z.ZodDefault<z.ZodEnum<{
4
+ wcag21aa: "wcag21aa";
5
+ wcag22aa: "wcag22aa";
6
+ "best-practice": "best-practice";
7
+ all: "all";
8
+ }>>;
9
+ scoreTargets: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodOptional<z.ZodNumber>, z.ZodRecord<z.ZodString, z.ZodNonOptional<z.ZodOptional<z.ZodNumber>>>]>>>;
10
+ timeout: z.ZodDefault<z.ZodNumber>;
11
+ }, z.core.$strip>;
12
+ export type AxePluginOptions = z.input<typeof axePluginOptionsSchema>;
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { pluginScoreTargetsSchema, positiveIntSchema, } from '@code-pushup/models';
3
+ import { AXE_DEFAULT_PRESET, AXE_PRESETS } from './constants.js';
4
+ export const axePluginOptionsSchema = z
5
+ .object({
6
+ preset: z.enum(AXE_PRESETS).default(AXE_DEFAULT_PRESET).meta({
7
+ description: 'Accessibility ruleset preset (default: wcag21aa for WCAG 2.1 Level AA compliance)',
8
+ }),
9
+ scoreTargets: pluginScoreTargetsSchema.optional(),
10
+ timeout: positiveIntSchema.default(30_000).meta({
11
+ description: 'Page navigation timeout in milliseconds (default: 30000ms / 30s)',
12
+ }),
13
+ })
14
+ .meta({
15
+ title: 'AxePluginOptions',
16
+ description: 'Configuration options for the Axe plugin',
17
+ });
18
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KACpC,MAAM,CAAC;IACN,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC;QAC3D,WAAW,EACT,mFAAmF;KACtF,CAAC;IACF,YAAY,EAAE,wBAAwB,CAAC,QAAQ,EAAE;IACjD,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;QAC9C,WAAW,EACT,kEAAkE;KACrE,CAAC;CACH,CAAC;KACD,IAAI,CAAC;IACJ,KAAK,EAAE,kBAAkB;IACzB,WAAW,EAAE,0CAA0C;CACxD,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const AXE_PLUGIN_SLUG = "axe";
2
+ export declare const AXE_PRESETS: readonly ["wcag21aa", "wcag22aa", "best-practice", "all"];
3
+ export type AxePreset = (typeof AXE_PRESETS)[number];
4
+ export declare const AXE_DEFAULT_PRESET: AxePreset;
@@ -0,0 +1,9 @@
1
+ export const AXE_PLUGIN_SLUG = 'axe';
2
+ export const AXE_PRESETS = [
3
+ 'wcag21aa',
4
+ 'wcag22aa',
5
+ 'best-practice',
6
+ 'all',
7
+ ];
8
+ export const AXE_DEFAULT_PRESET = 'wcag21aa';
9
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAErC,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,UAAU;IACV,UAAU;IACV,eAAe;IACf,KAAK;CACG,CAAC;AAIX,MAAM,CAAC,MAAM,kBAAkB,GAAc,UAAU,CAAC"}
@@ -0,0 +1,6 @@
1
+ import axe from 'axe-core';
2
+ import type { Audit, Group } from '@code-pushup/models';
3
+ import type { AxePreset } from '../constants.js';
4
+ export declare function loadAxeRules(preset: AxePreset): axe.RuleMetadata[];
5
+ export declare function transformRulesToAudits(rules: axe.RuleMetadata[]): Audit[];
6
+ export declare function transformRulesToGroups(rules: axe.RuleMetadata[], preset: AxePreset): Group[];
@@ -0,0 +1,102 @@
1
+ import axe from 'axe-core';
2
+ import { capitalize } from '@code-pushup/utils';
3
+ const WCAG_LEVEL_A_TAGS = ['wcag2a', 'wcag21a'];
4
+ const WCAG_LEVEL_AA_TAGS_21 = ['wcag2aa', 'wcag21aa'];
5
+ const WCAG_LEVEL_AA_TAGS_22 = ['wcag2aa', 'wcag21aa', 'wcag22aa'];
6
+ const CATEGORY_TITLES = {
7
+ 'cat.aria': 'ARIA',
8
+ 'cat.color': 'Color & Contrast',
9
+ 'cat.forms': 'Forms',
10
+ 'cat.keyboard': 'Keyboard',
11
+ 'cat.language': 'Language',
12
+ 'cat.name-role-value': 'Names & Labels',
13
+ 'cat.parsing': 'Parsing',
14
+ 'cat.semantics': 'Semantics',
15
+ 'cat.sensory-and-visual-cues': 'Visual Cues',
16
+ 'cat.structure': 'Structure',
17
+ 'cat.tables': 'Tables',
18
+ 'cat.text-alternatives': 'Text Alternatives',
19
+ 'cat.time-and-media': 'Media',
20
+ };
21
+ export function loadAxeRules(preset) {
22
+ const tags = getPresetTags(preset);
23
+ return tags.length === 0 ? axe.getRules() : axe.getRules(tags);
24
+ }
25
+ export function transformRulesToAudits(rules) {
26
+ return rules.map(rule => ({
27
+ slug: rule.ruleId,
28
+ title: rule.help,
29
+ description: rule.description,
30
+ docsUrl: rule.helpUrl,
31
+ }));
32
+ }
33
+ export function transformRulesToGroups(rules, preset) {
34
+ const groups = (() => {
35
+ switch (preset) {
36
+ case 'wcag21aa':
37
+ return createWcagGroups(rules, '2.1');
38
+ case 'wcag22aa':
39
+ return createWcagGroups(rules, '2.2');
40
+ case 'best-practice':
41
+ return createCategoryGroups(rules);
42
+ case 'all':
43
+ return [
44
+ ...createWcagGroups(rules, '2.2'),
45
+ ...createCategoryGroups(rules),
46
+ ];
47
+ }
48
+ })();
49
+ return groups.filter(({ refs }) => refs.length > 0);
50
+ }
51
+ /**
52
+ * Maps preset to corresponding axe-core tags.
53
+ *
54
+ * WCAG tags are non-cumulative - each rule has exactly one WCAG version tag.
55
+ * To include all rules up to a version/level, multiple tags must be combined.
56
+ */
57
+ function getPresetTags(preset) {
58
+ switch (preset) {
59
+ case 'wcag21aa':
60
+ return [...WCAG_LEVEL_A_TAGS, ...WCAG_LEVEL_AA_TAGS_21];
61
+ case 'wcag22aa':
62
+ return [...WCAG_LEVEL_A_TAGS, ...WCAG_LEVEL_AA_TAGS_22];
63
+ case 'best-practice':
64
+ return ['best-practice'];
65
+ case 'all':
66
+ return [];
67
+ }
68
+ }
69
+ function createGroup(slug, title, rules) {
70
+ return {
71
+ slug,
72
+ title,
73
+ refs: rules.map(({ ruleId }) => ({ slug: ruleId, weight: 1 })),
74
+ };
75
+ }
76
+ function createWcagGroups(rules, version) {
77
+ const aTags = WCAG_LEVEL_A_TAGS;
78
+ const aaTags = version === '2.1' ? WCAG_LEVEL_AA_TAGS_21 : WCAG_LEVEL_AA_TAGS_22;
79
+ const levelARules = rules.filter(({ tags }) => tags.some(tag => aTags.includes(tag)));
80
+ const levelAARules = rules.filter(({ tags }) => tags.some(tag => aaTags.includes(tag)));
81
+ const versionSlug = version.replace('.', '');
82
+ return [
83
+ createGroup(`wcag${versionSlug}-level-a`, `WCAG ${version} Level A`, levelARules),
84
+ createGroup(`wcag${versionSlug}-level-aa`, `WCAG ${version} Level AA`, levelAARules),
85
+ ];
86
+ }
87
+ function createCategoryGroups(rules) {
88
+ const categoryTags = new Set(rules.flatMap(({ tags }) => tags.filter(tag => tag.startsWith('cat.'))));
89
+ return Array.from(categoryTags).map(tag => {
90
+ const slug = tag.replace('cat.', '');
91
+ const title = formatCategoryTitle(tag, slug);
92
+ const categoryRules = rules.filter(({ tags }) => tags.includes(tag));
93
+ return createGroup(slug, title, categoryRules);
94
+ });
95
+ }
96
+ function formatCategoryTitle(tag, slug) {
97
+ if (CATEGORY_TITLES[tag]) {
98
+ return CATEGORY_TITLES[tag];
99
+ }
100
+ return slug.split('-').map(capitalize).join(' ');
101
+ }
102
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../../src/lib/meta/transform.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAChD,MAAM,qBAAqB,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AACtD,MAAM,qBAAqB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAElE,MAAM,eAAe,GAA2B;IAC9C,UAAU,EAAE,MAAM;IAClB,WAAW,EAAE,kBAAkB;IAC/B,WAAW,EAAE,OAAO;IACpB,cAAc,EAAE,UAAU;IAC1B,cAAc,EAAE,UAAU;IAC1B,qBAAqB,EAAE,gBAAgB;IACvC,aAAa,EAAE,SAAS;IACxB,eAAe,EAAE,WAAW;IAC5B,6BAA6B,EAAE,aAAa;IAC5C,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,QAAQ;IACtB,uBAAuB,EAAE,mBAAmB;IAC5C,oBAAoB,EAAE,OAAO;CAC9B,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAyB;IAC9D,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,KAAK,EAAE,IAAI,CAAC,IAAI;QAChB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,KAAyB,EACzB,MAAiB;IAEjB,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE;QACnB,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACxC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACxC,KAAK,eAAe;gBAClB,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;YACrC,KAAK,KAAK;gBACR,OAAO;oBACL,GAAG,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;oBACjC,GAAG,oBAAoB,CAAC,KAAK,CAAC;iBAC/B,CAAC;QACN,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,MAAiB;IACtC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,CAAC,GAAG,iBAAiB,EAAE,GAAG,qBAAqB,CAAC,CAAC;QAC1D,KAAK,UAAU;YACb,OAAO,CAAC,GAAG,iBAAiB,EAAE,GAAG,qBAAqB,CAAC,CAAC;QAC1D,KAAK,eAAe;YAClB,OAAO,CAAC,eAAe,CAAC,CAAC;QAC3B,KAAK,KAAK;YACR,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAAY,EACZ,KAAa,EACb,KAAyB;IAEzB,OAAO;QACL,IAAI;QACJ,KAAK;QACL,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;KAC/D,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAyB,EACzB,OAAsB;IAEtB,MAAM,KAAK,GAAG,iBAAiB,CAAC;IAChC,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAEpE,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACtC,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACvC,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAE7C,OAAO;QACL,WAAW,CACT,OAAO,WAAW,UAAU,EAC5B,QAAQ,OAAO,UAAU,EACzB,WAAW,CACZ;QACD,WAAW,CACT,OAAO,WAAW,WAAW,EAC7B,QAAQ,OAAO,WAAW,EAC1B,YAAY,CACb;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAyB;IACrD,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CACxE,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QACxC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7C,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAErE,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW,EAAE,IAAY;IACpD,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Audit, Group } from '@code-pushup/models';
2
+ import type { AxePreset } from './constants.js';
3
+ export declare function processAuditsAndGroups(urls: string[], preset: AxePreset): {
4
+ audits: Audit[];
5
+ groups: Group[];
6
+ ruleIds: string[];
7
+ };
@@ -0,0 +1,17 @@
1
+ import { expandAuditsForUrls, expandGroupsForUrls, shouldExpandForUrls, } from '@code-pushup/utils';
2
+ import { loadAxeRules, transformRulesToAudits, transformRulesToGroups, } from './meta/transform.js';
3
+ export function processAuditsAndGroups(urls, preset) {
4
+ const rules = loadAxeRules(preset);
5
+ const ruleIds = rules.map(({ ruleId }) => ruleId);
6
+ const audits = transformRulesToAudits(rules);
7
+ const groups = transformRulesToGroups(rules, preset);
8
+ if (!shouldExpandForUrls(urls.length)) {
9
+ return { audits, groups, ruleIds };
10
+ }
11
+ return {
12
+ audits: expandAuditsForUrls(audits, urls),
13
+ groups: expandGroupsForUrls(groups, urls),
14
+ ruleIds,
15
+ };
16
+ }
17
+ //# sourceMappingURL=processing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processing.js","sourceRoot":"","sources":["../../../src/lib/processing.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,UAAU,sBAAsB,CACpC,IAAc,EACd,MAAiB;IAMjB,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAErD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IAED,OAAO;QACL,MAAM,EAAE,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC;QACzC,MAAM,EAAE,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC;QACzC,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AuditOutputs } from '@code-pushup/models';
2
+ export declare function runAxeForUrl(url: string, ruleIds: string[], timeout: number): Promise<AuditOutputs>;
3
+ export declare function closeBrowser(): Promise<void>;
@@ -0,0 +1,64 @@
1
+ import AxeBuilder from '@axe-core/playwright';
2
+ import { chromium } from 'playwright-core';
3
+ import { executeProcess, logger, pluralizeToken, stringifyError, } from '@code-pushup/utils';
4
+ import { toAuditOutputs } from './transform.js';
5
+ let browser;
6
+ let browserChecked = false;
7
+ export async function runAxeForUrl(url, ruleIds, timeout) {
8
+ try {
9
+ if (!browser) {
10
+ await ensureBrowserInstalled();
11
+ logger.debug('Launching Chromium browser...');
12
+ browser = await chromium.launch({ headless: true });
13
+ }
14
+ const context = await browser.newContext();
15
+ try {
16
+ const page = await context.newPage();
17
+ try {
18
+ await page.goto(url, {
19
+ waitUntil: 'networkidle',
20
+ timeout,
21
+ });
22
+ const axeBuilder = new AxeBuilder({ page });
23
+ // Use withRules() to include experimental/deprecated rules
24
+ if (ruleIds.length > 0) {
25
+ axeBuilder.withRules(ruleIds);
26
+ }
27
+ const results = await axeBuilder.analyze();
28
+ const incompleteCount = results.incomplete.length;
29
+ if (incompleteCount > 0) {
30
+ logger.warn(`Axe returned ${pluralizeToken('incomplete result', incompleteCount)} for ${url}`);
31
+ }
32
+ return toAuditOutputs(results, url);
33
+ }
34
+ finally {
35
+ await page.close();
36
+ }
37
+ }
38
+ finally {
39
+ await context.close();
40
+ }
41
+ }
42
+ catch (error) {
43
+ logger.error(`Axe execution failed for ${url}: ${stringifyError(error)}`);
44
+ throw error;
45
+ }
46
+ }
47
+ export async function closeBrowser() {
48
+ if (browser) {
49
+ await browser.close();
50
+ browser = undefined;
51
+ }
52
+ }
53
+ async function ensureBrowserInstalled() {
54
+ if (browserChecked) {
55
+ return;
56
+ }
57
+ logger.debug('Checking Chromium browser installation...');
58
+ await executeProcess({
59
+ command: 'npx',
60
+ args: ['playwright-core', 'install', 'chromium'],
61
+ });
62
+ browserChecked = true;
63
+ }
64
+ //# sourceMappingURL=run-axe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-axe.js","sourceRoot":"","sources":["../../../../src/lib/runner/run-axe.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAgB,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EACL,cAAc,EACd,MAAM,EACN,cAAc,EACd,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,IAAI,OAA4B,CAAC;AACjC,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,OAAiB,EACjB,OAAe;IAEf,IAAI,CAAC;QACH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,sBAAsB,EAAE,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YAC9C,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAE3C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;oBACnB,SAAS,EAAE,aAAa;oBACxB,OAAO;iBACR,CAAC,CAAC;gBAEH,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;gBAE5C,2DAA2D;gBAC3D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvB,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;gBAChC,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;gBAE3C,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;gBAElD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,CAAC,IAAI,CACT,gBAAgB,cAAc,CAAC,mBAAmB,EAAE,eAAe,CAAC,QAAQ,GAAG,EAAE,CAClF,CAAC;gBACJ,CAAC;gBAED,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACtC,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,4BAA4B,GAAG,KAAK,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,sBAAsB;IACnC,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAE1D,MAAM,cAAc,CAAC;QACnB,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,iBAAiB,EAAE,SAAS,EAAE,UAAU,CAAC;KACjD,CAAC,CAAC;IAEH,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { RunnerFunction } from '@code-pushup/models';
2
+ export declare function createRunnerFunction(urls: string[], ruleIds: string[], timeout: number): RunnerFunction;
@@ -0,0 +1,41 @@
1
+ import { addIndex, logger, pluralizeToken, shouldExpandForUrls, stringifyError, } from '@code-pushup/utils';
2
+ import { closeBrowser, runAxeForUrl } from './run-axe.js';
3
+ export function createRunnerFunction(urls, ruleIds, timeout) {
4
+ return async (_runnerArgs) => {
5
+ const urlCount = urls.length;
6
+ const isSingleUrl = !shouldExpandForUrls(urlCount);
7
+ logger.info(`Running Axe accessibility checks for ${pluralizeToken('URL', urlCount)}...`);
8
+ try {
9
+ const allResults = await urls.reduce(async (prev, url, index) => {
10
+ const acc = await prev;
11
+ logger.debug(`Testing URL ${index + 1}/${urlCount}: ${url}`);
12
+ try {
13
+ const auditOutputs = await runAxeForUrl(url, ruleIds, timeout);
14
+ const processedOutputs = isSingleUrl
15
+ ? auditOutputs
16
+ : auditOutputs.map(audit => ({
17
+ ...audit,
18
+ slug: addIndex(audit.slug, index),
19
+ }));
20
+ return [...acc, ...processedOutputs];
21
+ }
22
+ catch (error) {
23
+ logger.warn(stringifyError(error));
24
+ return acc;
25
+ }
26
+ }, Promise.resolve([]));
27
+ const totalAuditCount = allResults.length;
28
+ if (totalAuditCount === 0) {
29
+ throw new Error(isSingleUrl
30
+ ? 'Axe did not produce any results.'
31
+ : 'Axe failed to produce results for all URLs.');
32
+ }
33
+ logger.info(`Completed Axe accessibility checks with ${pluralizeToken('audit', totalAuditCount)}`);
34
+ return allResults;
35
+ }
36
+ finally {
37
+ await closeBrowser();
38
+ }
39
+ };
40
+ }
41
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../src/lib/runner/runner.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,QAAQ,EACR,MAAM,EACN,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,UAAU,oBAAoB,CAClC,IAAc,EACd,OAAiB,EACjB,OAAe;IAEf,OAAO,KAAK,EAAE,WAAwB,EAAyB,EAAE;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;QAC7B,MAAM,WAAW,GAAG,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAEnD,MAAM,CAAC,IAAI,CACT,wCAAwC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAC7E,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;gBAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC;gBAEvB,MAAM,CAAC,KAAK,CAAC,eAAe,KAAK,GAAG,CAAC,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC,CAAC;gBAE7D,IAAI,CAAC;oBACH,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;oBAE/D,MAAM,gBAAgB,GAAG,WAAW;wBAClC,CAAC,CAAC,YAAY;wBACd,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BACzB,GAAG,KAAK;4BACR,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;yBAClC,CAAC,CAAC,CAAC;oBAER,OAAO,CAAC,GAAG,GAAG,EAAE,GAAG,gBAAgB,CAAC,CAAC;gBACvC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;oBACnC,OAAO,GAAG,CAAC;gBACb,CAAC;YACH,CAAC,EAAE,OAAO,CAAC,OAAO,CAAe,EAAE,CAAC,CAAC,CAAC;YAEtC,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC;YAE1C,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CACb,WAAW;oBACT,CAAC,CAAC,kCAAkC;oBACpC,CAAC,CAAC,6CAA6C,CAClD,CAAC;YACJ,CAAC;YAED,MAAM,CAAC,IAAI,CACT,2CAA2C,cAAc,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,CACtF,CAAC;YAEF,OAAO,UAAU,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,MAAM,YAAY,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { AxeResults } from 'axe-core';
2
+ import type { AuditOutputs } from '@code-pushup/models';
3
+ /**
4
+ * Transforms Axe results into audit outputs.
5
+ * Priority: violations > incomplete > passes > inapplicable
6
+ */
7
+ export declare function toAuditOutputs({ passes, violations, incomplete, inapplicable }: AxeResults, url: string): AuditOutputs;
@@ -0,0 +1,67 @@
1
+ import { formatIssueSeverities, getUrlIdentifier, pluralizeToken, truncateIssueMessage, } from '@code-pushup/utils';
2
+ /**
3
+ * Transforms Axe results into audit outputs.
4
+ * Priority: violations > incomplete > passes > inapplicable
5
+ */
6
+ export function toAuditOutputs({ passes, violations, incomplete, inapplicable }, url) {
7
+ const auditMap = new Map([
8
+ ...inapplicable.map(res => [res.id, toAuditOutput(res, url, 1)]),
9
+ ...passes.map(res => [res.id, toAuditOutput(res, url, 1)]),
10
+ ...incomplete.map(res => [res.id, toAuditOutput(res, url, 0)]),
11
+ ...violations.map(res => [res.id, toAuditOutput(res, url, 0)]),
12
+ ]);
13
+ return Array.from(auditMap.values());
14
+ }
15
+ /**
16
+ * For failing audits (score 0), includes detailed issues with locations and severities.
17
+ * For passing audits (score 1), only includes element count.
18
+ */
19
+ function toAuditOutput(result, url, score) {
20
+ const base = {
21
+ slug: result.id,
22
+ score,
23
+ value: result.nodes.length,
24
+ };
25
+ if (score === 0 && result.nodes.length > 0) {
26
+ const issues = result.nodes.map(node => toIssue(node, result, url));
27
+ return {
28
+ ...base,
29
+ displayValue: formatIssueSeverities(issues),
30
+ details: { issues },
31
+ };
32
+ }
33
+ return {
34
+ ...base,
35
+ displayValue: pluralizeToken('element', result.nodes.length),
36
+ };
37
+ }
38
+ function formatSelector(selector) {
39
+ if (typeof selector === 'string') {
40
+ return selector;
41
+ }
42
+ return selector.join(' >> ');
43
+ }
44
+ function toIssue(node, result, url) {
45
+ const selector = formatSelector(node.target?.[0] || node.html);
46
+ const rawMessage = node.failureSummary || result.help;
47
+ const cleanedMessage = rawMessage.replace(/\s+/g, ' ').trim();
48
+ const message = `[\`${selector}\`] ${cleanedMessage} ([${getUrlIdentifier(url)}](${url}))`;
49
+ return {
50
+ message: truncateIssueMessage(message),
51
+ severity: impactToSeverity(node.impact),
52
+ };
53
+ }
54
+ function impactToSeverity(impact) {
55
+ switch (impact) {
56
+ case 'critical':
57
+ case 'serious':
58
+ return 'error';
59
+ case 'moderate':
60
+ return 'warning';
61
+ case 'minor':
62
+ case null:
63
+ case undefined:
64
+ return 'info';
65
+ }
66
+ }
67
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../../src/lib/runner/transform.ts"],"names":[],"mappings":"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"}