Skip to content

A11y: make the axe peer deps optional#2485

Open
fastfrwrd wants to merge 3 commits intomainfrom
fastfrwrd/optional-dep-axe
Open

A11y: make the axe peer deps optional#2485
fastfrwrd wants to merge 3 commits intomainfrom
fastfrwrd/optional-dep-axe

Conversation

@fastfrwrd
Copy link
Contributor

@fastfrwrd fastfrwrd commented Feb 25, 2026

I'm a little concerned about the peerDependencies in 3.4.0 after integrating in Grafana, in case some plugin authors don't want to use the a11y tests, so I'm going to see if they can be made optional in an elegant way. We should maybe release this as 3.4.1 in case 3.4.0 is a problem for folks.

📦 Published PR as canary version: Canary Versions

✨ Test out this PR locally via:

npm install @grafana/plugin-e2e@3.4.1-canary.2485.22416356262.0
# or 
yarn add @grafana/plugin-e2e@3.4.1-canary.2485.22416356262.0

@fastfrwrd fastfrwrd requested a review from a team as a code owner February 25, 2026 20:54
@fastfrwrd fastfrwrd requested review from Copilot and s4kh and removed request for Copilot February 25, 2026 20:54
@fastfrwrd fastfrwrd added patch Increment the patch version when merged release Create a release when this pr is merged labels Feb 25, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

Hello! 👋 This repository uses Auto for releasing packages using PR labels.

✨ This PR can be merged and will trigger a new patch release.
NOTE: When merging a PR with the release label please avoid merging another PR. For further information see here.

@fastfrwrd
Copy link
Contributor Author

I assume I will have to import them differently for this to actually work, but I'm going to see how the canary does against Grafana now.

Copilot AI review requested due to automatic review settings February 25, 2026 21:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the axe-core accessibility testing peer dependencies optional to avoid forcing plugin authors to install dependencies they may not need. The changes convert regular imports to type-only imports and use dynamic imports with error handling for runtime usage.

Changes:

  • Added peerDependenciesMeta to mark @axe-core/playwright and axe-core as optional peer dependencies
  • Converted static imports to type-only imports using import type syntax
  • Implemented dynamic import with try-catch for runtime usage in the fixture

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/plugin-e2e/package.json Added peerDependenciesMeta to mark axe-core dependencies as optional
packages/plugin-e2e/src/matchers/toHaveNoA11yViolations.ts Converted imports to type-only imports for optional dependencies
packages/plugin-e2e/src/fixtures/scanForA11yViolations.ts Added dynamic import with error handling for optional dependency

return accessibilityScanResults;
});
} catch (error) {
throw new Error('@axe-core/playwright must be installed as a peer dependency to use a11y scanning.');
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling is incorrect for this fixture. When the dynamic import fails, the catch block throws an error without calling use(), which will cause the fixture setup itself to fail. This means tests that declare the fixture in their parameters will fail even if they never use it.

Instead, the fixture should call use() with a function that throws the error when invoked. This way, only tests that actually call scanForA11yViolations() will see the error, while tests that merely have the fixture available (but don't use it) will still pass.

Suggested change
throw new Error('@axe-core/playwright must be installed as a peer dependency to use a11y scanning.');
await use(async () => {
throw new Error('@axe-core/playwright must be installed as a peer dependency to use a11y scanning.');
});

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

point taken; wrote it a different way.

@grafana-plugins-platform-bot grafana-plugins-platform-bot bot moved this from 📬 Triage to 🔬 In review in Grafana Catalog Team Feb 25, 2026
@fastfrwrd fastfrwrd requested a review from a team as a code owner February 25, 2026 21:18
@github-actions
Copy link
Contributor

Playwright test results

Image Name Version Result Report
grafana-dev 13.0.0-22414747665
grafana-enterprise 12.4.0
grafana-enterprise 12.0.10
grafana-enterprise 10.4.19
grafana-enterprise 9.3.16
grafana-enterprise 8.5.27
Troubleshooting

404 when clicking on View report

By default, the deploy-report-pages Action deploys reports to the gh-pages branch. However, you need to take an extra step to ensure that GitHub Pages can build and serve the site from this branch. To do so:

  1. Go to the Settings tab of your repository.
  2. In the left-hand sidebar, click on Pages.
  3. Under Source, select Deploy from a branch, then choose the gh-pages branch.

This action needs to be completed manually in order for your GitHub Pages site to be built and accessible from the gh-pages branch. Once configured, GitHub will automatically build and serve the site whenever new reports are deployed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

packages/plugin-e2e/src/fixtures/scanForA11yViolations.ts:16

  • Even with peerDependenciesMeta.optional, importing AxeResults from axe-core in this exported fixture type means TypeScript consumers may still need axe-core installed just to typecheck (because the generated .d.ts will reference axe-core). If the goal is truly “optional unless you use a11y”, consider replacing AxeResults in the public signature with an internal minimal result type (or unknown/structural type) so projects that don’t use a11y don’t need the axe packages for TS resolution.
import { TestFixture } from '@playwright/test';
import type { AxeResults } from 'axe-core';
import type { AxeBuilder as AxeBuilderType } from '@axe-core/playwright';

import { PlaywrightArgs, AxeScanContext } from '../types';

export const DEFAULT_A11Y_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa' /* 'best-practice' */];

let AxeBuilder: typeof AxeBuilderType;

/**
 * @alpha - the API for accessibility scanning is still being finalized and may change in future releases. Feedback is welcome!
 */
export const scanForA11yViolations: TestFixture<
  (context?: AxeScanContext) => Promise<AxeResults>,
  PlaywrightArgs

packages/plugin-e2e/src/matchers/toHaveNoA11yViolations.ts:13

  • AxeResults is pulled from axe-core (now an optional peer), but this matcher’s public API still references that external type. For TS consumers who don’t install the optional peer deps, this can still break typechecking because the emitted .d.ts will reference axe-core. To make the dependency truly optional, consider changing the matcher signature/docs to use a local structural “axe results” type (only the fields you access: violations[].id/description/helpUrl/impact/nodes[].html) instead of axe-core’s exported type.
import type { MatcherReturnType } from '@playwright/test';
import type { AxeResults } from 'axe-core';

import { A11yViolationsOptions } from '../types';

/**
 * @alpha - the API is not yet stable and may change without a major version bump. Use with caution.
 * @param {AxeResults} results  - The results from an Axe accessibility scan.
 * @param {A11yViolationsOptions} options - Options for configuring the accessibility violations check.
 * @returns {MatcherReturnType} - The result of the accessibility violations check.
 */
export function toHaveNoA11yViolations(results: AxeResults, options?: A11yViolationsOptions): MatcherReturnType {
  const threshold = options?.threshold ?? 0;

Comment on lines +21 to +23
try {
const { AxeBuilder: _AxeBuilder } = await import('@axe-core/playwright');
AxeBuilder = _AxeBuilder;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic import is pulling a named export ({ AxeBuilder }), but this module was previously imported as the default export (import AxeBuilder from '@axe-core/playwright'). If @axe-core/playwright only provides a default export, _AxeBuilder will be undefined and new AxeBuilder({ page }) will crash. Align the dynamic import (and the AxeBuilderType typing) with the package’s actual export shape (likely default).

Copilot uses AI. Check for mistakes.
const { AxeBuilder: _AxeBuilder } = await import('@axe-core/playwright');
AxeBuilder = _AxeBuilder;
} catch (error) {
throw new Error('@axe-core/playwright must be installed as a peer dependency to use a11y scanning.');
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thrown message says only @axe-core/playwright must be installed, but axe-core is also declared as an (optional) peer and may be the actual missing module that makes this import fail. Update the error to mention both packages (or make it generic like “axe a11y peer deps”) so users know what to install.

Suggested change
throw new Error('@axe-core/playwright must be installed as a peer dependency to use a11y scanning.');
throw new Error('@axe-core/playwright and axe-core must be installed (as peer dependencies) to use a11y scanning.');

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that optional dependency only impacts TypeScript types, which seem to not be an issue unless we forget to import type, so we don't care I think.

@fastfrwrd
Copy link
Contributor Author

fastfrwrd commented Feb 25, 2026

I tested this via the canary on grafana/grafana and the Polystat plugin - this change does effectively make the peer dependency optional. When you run an a11y test in Polystat, you get the more useful error, and when you install the Axe dep, the test passes.

final version of the branch is here

  • try deleting the @axe-core/playwright dependency
  • also try deleting the dependency and then moving down to 3.4.0 of plugin-e2e, all e2es fail in there (which is what I want to avoid)
Screenshot 2026-02-25 at 5 35 21 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Increment the patch version when merged release Create a release when this pr is merged

Projects

Status: 🔬 In review

Development

Successfully merging this pull request may close these issues.

2 participants