diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 4ea96774f..a80c0ce23 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -1,5 +1,6 @@ import 'dotenv/config'; import { + axeCoreConfig, coverageCoreConfigNx, eslintCoreConfigNx, jsDocsCoreConfig, @@ -43,4 +44,7 @@ export default mergeConfigs( '!**/implementation/**', '!**/internal/**', ]), + axeCoreConfig( + 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', + ), ); diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 34fb85c10..fda87e20c 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -2,7 +2,9 @@ import type { CategoryConfig, CoreConfig, + PluginUrls, } from './packages/models/src/index.js'; +import axePlugin from './packages/plugin-axe/src/index.js'; import coveragePlugin, { getNxCoveragePaths, } from './packages/plugin-coverage/src/index.js'; @@ -20,7 +22,6 @@ import { } from './packages/plugin-jsdocs/src/lib/constants.js'; import { filterGroupsByOnlyAudits } from './packages/plugin-jsdocs/src/lib/utils.js'; import lighthousePlugin, { - type LighthouseUrls, lighthouseGroupRef, mergeLighthouseCategories, } from './packages/plugin-lighthouse/src/index.js'; @@ -137,7 +138,7 @@ export const jsPackagesCoreConfig = async (): Promise => ({ }); export const lighthouseCoreConfig = async ( - urls: LighthouseUrls, + urls: PluginUrls, ): Promise => { const lhPlugin = await lighthousePlugin(urls); return { @@ -216,3 +217,7 @@ export const coverageCoreConfigNx = async ( categories: coverageCategories, }; }; + +export const axeCoreConfig = (urls: PluginUrls): CoreConfig => ({ + plugins: [axePlugin(urls)], +}); diff --git a/e2e/plugin-axe-e2e/eslint.config.js b/e2e/plugin-axe-e2e/eslint.config.js new file mode 100644 index 000000000..2656b27cb --- /dev/null +++ b/e2e/plugin-axe-e2e/eslint.config.js @@ -0,0 +1,12 @@ +import tseslint from 'typescript-eslint'; +import baseConfig from '../../eslint.config.js'; + +export default tseslint.config(...baseConfig, { + files: ['**/*.ts'], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, +}); diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/code-pushup.config.ts b/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/code-pushup.config.ts new file mode 100644 index 000000000..da9aa4153 --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/code-pushup.config.ts @@ -0,0 +1,11 @@ +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import axePlugin from '@code-pushup/axe-plugin'; +import type { CoreConfig } from '@code-pushup/models'; + +const htmlFile = join(process.cwd(), 'index.html'); +const url = pathToFileURL(htmlFile).href; + +export default { + plugins: [axePlugin(url)], +} satisfies CoreConfig; diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/index.html b/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/index.html new file mode 100644 index 000000000..92c2112c6 --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/default-setup/index.html @@ -0,0 +1,44 @@ + + + + + + Accessibility Test Page + + + +

Accessibility Test Page

+ + + + + +
+ This text has poor color contrast and may be hard to read. +
+ + +
+ Button with invalid ARIA attribute +
+ + +
+ + +
+ + + + + + + + diff --git a/e2e/plugin-axe-e2e/project.json b/e2e/plugin-axe-e2e/project.json new file mode 100644 index 000000000..972319ed4 --- /dev/null +++ b/e2e/plugin-axe-e2e/project.json @@ -0,0 +1,17 @@ +{ + "name": "plugin-axe-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/plugin-axe-e2e/src", + "projectType": "application", + "targets": { + "lint": {}, + "e2e": { + "executor": "@nx/vite:test", + "options": { + "configFile": "{projectRoot}/vitest.e2e.config.ts" + } + } + }, + "implicitDependencies": ["cli", "plugin-axe"], + "tags": ["scope:plugin", "type:e2e"] +} diff --git a/e2e/plugin-axe-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-axe-e2e/tests/__snapshots__/collect.e2e.test.ts.snap new file mode 100644 index 000000000..96d1f5c6a --- /dev/null +++ b/e2e/plugin-axe-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -0,0 +1,963 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin over CLI and create report.json 1`] = ` +{ + "packageName": "@code-pushup/core", + "plugins": [ + { + "audits": [ + { + "description": "Ensure elements of image maps have alternative text", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/area-alt?application=axeAPI", + "score": 1, + "slug": "area-alt", + "title": "Active elements must have alternative text", + "value": 0, + }, + { + "description": "Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-braille-equivalent?application=axeAPI", + "score": 1, + "slug": "aria-braille-equivalent", + "title": "aria-braille attributes must have a non-braille equivalent", + "value": 0, + }, + { + "description": "Ensure aria-hidden elements are not focusable nor contain focusable elements", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-hidden-focus?application=axeAPI", + "score": 1, + "slug": "aria-hidden-focus", + "title": "ARIA hidden element must not be focusable or contain focusable elements", + "value": 0, + }, + { + "description": "Ensure every ARIA input field has an accessible name", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-input-field-name?application=axeAPI", + "score": 1, + "slug": "aria-input-field-name", + "title": "ARIA input fields must have an accessible name", + "value": 0, + }, + { + "description": "Ensure every ARIA meter node has an accessible name", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-meter-name?application=axeAPI", + "score": 1, + "slug": "aria-meter-name", + "title": "ARIA meter nodes must have an accessible name", + "value": 0, + }, + { + "description": "Ensure every ARIA progressbar node has an accessible name", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-progressbar-name?application=axeAPI", + "score": 1, + "slug": "aria-progressbar-name", + "title": "ARIA progressbar nodes must have an accessible name", + "value": 0, + }, + { + "description": "Ensure elements with an ARIA role that require child roles contain them", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-required-children?application=axeAPI", + "score": 1, + "slug": "aria-required-children", + "title": "Certain ARIA roles must contain particular children", + "value": 0, + }, + { + "description": "Ensure elements with an ARIA role that require parent roles are contained by them", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-required-parent?application=axeAPI", + "score": 1, + "slug": "aria-required-parent", + "title": "Certain ARIA roles must be contained by particular parents", + "value": 0, + }, + { + "description": "Ensure aria-roledescription is only used on elements with an implicit or explicit role", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-roledescription?application=axeAPI", + "score": 1, + "slug": "aria-roledescription", + "title": "aria-roledescription must be on elements with a semantic role", + "value": 0, + }, + { + "description": "Ensure every ARIA toggle field has an accessible name", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-toggle-field-name?application=axeAPI", + "score": 1, + "slug": "aria-toggle-field-name", + "title": "ARIA toggle fields must have an accessible name", + "value": 0, + }, + { + "description": "Ensure every ARIA tooltip node has an accessible name", + "displayValue": "0 elements", + "docsUrl": "https://dequeuniversity.com/rules/axe/4.11/aria-tooltip-name?application=axeAPI", + "score": 1, + "slug": "aria-tooltip-name", + "title": "ARIA tooltip nodes must have an accessible name", + "value": 0, + }, + { + "description": "Ensure