diff --git a/CHANGELOG.md b/CHANGELOG.md index a72e28b..2afc605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [new] Add parameter for Axe `context` [#98](https://github.com/chanzuckerberg/axe-storybook-testing/pull/98) + ## 8.1.0 - [new] Add `--port` option [#97](https://github.com/chanzuckerberg/axe-storybook-testing/pull/97) diff --git a/README.md b/README.md index e3ac6a2..f9c77ba 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ If there are any violations, information about them will be printed, and the com - [disabledRules](#disabledrules) - [mode](#mode) - [runOptions](#runoptions) + - [context](#context) + - [config](#config) - [skip](#skip) - [timeout](#timeout) - [waitForSelector](#waitforselector) (deprecated) @@ -153,7 +155,6 @@ export const parameters = { Allows use of any of the available [`axe.run`](https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter) options. See the link for more details. When using `runOptions.rules` in combination with `disabledRules`, **`disabledRules` will always take precedent.** ```jsx - export const SomeStory = { parameters: { axe: { @@ -167,6 +168,22 @@ export const SomeStory = { } ``` +### context + +[Axe context](https://www.deque.com/axe/core-documentation/api-documentation/#context-parameter), which is passed to `axe.run`. Useful for including or excluding elements from the tests. + +```jsx +export const SomeStory = { + parameters: { + axe: { + context: { + exclude: '.foo', + }, + } + } +} +``` + ### config Axe configuration, which is passed to [axe.configure](https://www.deque.com/axe/core-documentation/api-documentation/#api-name-axeconfigure). diff --git a/demo/src/advanced.stories.jsx b/demo/src/advanced.stories.jsx index 72194ce..b9deb7f 100644 --- a/demo/src/advanced.stories.jsx +++ b/demo/src/advanced.stories.jsx @@ -21,3 +21,26 @@ export const branding = { }, }, }; + +// Test out passing context to `axe.run`. +export const multipleParts = { + render: () => ( +
+
+ +
+
+ +
+
+ ), + parameters: { + axe: { + context: '#a', + }, + }, +}; diff --git a/index.d.ts b/index.d.ts index 940a213..22ff468 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,9 @@ -import type {RunOptions, Spec} from 'axe-core'; +import type { + RunOptions, + SerialContextObject, + SerialFrameSelector, + Spec, +} from 'axe-core'; export declare type AxeParams = { /** @@ -26,6 +31,11 @@ export declare type AxeParams = { * @see https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter */ runOptions?: RunOptions; + /** + * Context passed to `axe.run`. + * @see https://www.deque.com/axe/core-documentation/api-documentation/#context-parameter + */ + context?: SerialFrameSelector | SerialFrameSelector[] | SerialContextObject; /** * Config passed to `axe.configure`. */ diff --git a/src/ProcessedStory.ts b/src/ProcessedStory.ts index d844cbd..75775d9 100644 --- a/src/ProcessedStory.ts +++ b/src/ProcessedStory.ts @@ -1,11 +1,13 @@ import type {RunOptions, Spec} from 'axe-core'; import {z as zod} from 'zod'; +import type {Context} from './browser/AxePage'; import type {StorybookStory} from './browser/StorybookPage'; type Params = { disabledRules: string[]; mode: 'off' | 'warn' | 'error'; runOptions?: RunOptions; + context?: Context; config?: Spec; skip: boolean; timeout: number; @@ -42,6 +44,9 @@ export default class ProcessedStory { rawStory.parameters?.axe?.runOptions, rawStory, ), + context: normalizeContext(rawStory.parameters?.axe?.context, rawStory) as + | Context + | undefined, config: normalizeConfig(rawStory.parameters?.axe?.config, rawStory), }; } @@ -75,6 +80,14 @@ export default class ProcessedStory { return this.parameters.runOptions; } + /** + * Context passed to `axe.run`. + * @see https://www.deque.com/axe/core-documentation/api-documentation/#context-parameter + */ + get context() { + return this.parameters.context; + } + /** * All optional config used to configure axe-core. Passed to `axe.configure`. * @see https://www.deque.com/axe/core-documentation/api-documentation/#api-name-axeconfigure @@ -138,6 +151,14 @@ const runOptionsSchema = zod.optional( }), ); +const contextSchema = zod + .union([ + zod.string(), + zod.array(zod.string()), + zod.record(zod.string(), zod.any()), + ]) + .optional(); + const configSchema = zod.object({}).passthrough().optional(); function normalizeSkip(skip: unknown, rawStory: StorybookStory) { @@ -175,6 +196,14 @@ function normalizeRunOptions(runOptions: unknown, rawStory: StorybookStory) { ); } +function normalizeContext(config: unknown, rawStory: StorybookStory) { + return parseWithFriendlyError( + () => contextSchema.parse(config), + rawStory, + 'context', + ); +} + function normalizeConfig(config: unknown, rawStory: StorybookStory) { return parseWithFriendlyError( () => configSchema.parse(config), diff --git a/src/Result.ts b/src/Result.ts index 5addd57..54b4a40 100644 --- a/src/Result.ts +++ b/src/Result.ts @@ -29,6 +29,7 @@ export default class Result { page, disabledRules, story.runOptions, + story.context, story.config, ); return new Result(axeResults.violations); diff --git a/src/browser/AxePage.ts b/src/browser/AxePage.ts index 5eb578f..be0398c 100644 --- a/src/browser/AxePage.ts +++ b/src/browser/AxePage.ts @@ -1,6 +1,18 @@ -import type {AxeResults, RuleObject, RunOptions, Spec} from 'axe-core'; +import type { + AxeResults, + SerialContextObject, + SerialFrameSelector, + RuleObject, + RunOptions, + Spec, +} from 'axe-core'; import type {Page} from 'playwright'; +export type Context = + | SerialFrameSelector + | SerialFrameSelector[] + | SerialContextObject; + /** * Prepare a page for running axe on it. */ @@ -17,20 +29,24 @@ export function analyze( page: Page, disabledRules: string[] = [], runOptions: RunOptions = {}, + context?: Context, config?: Spec, ): Promise { return page.evaluate(runAxe, { options: getRunOptions(runOptions, disabledRules), config, + context, }); } function runAxe({ - options, config, + context, + options, }: { - options: RunOptions; config?: Spec; + context?: Context; + options: RunOptions; }): Promise { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This function executes in a browser context. @@ -49,7 +65,7 @@ function runAxe({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore This function executes in a browser context. - return window.axe.run(document, options); + return window.axe.run(context || document, options); }); } diff --git a/tests/integration/__snapshots__/integration.test.ts.snap b/tests/integration/__snapshots__/integration.test.ts.snap index 540cab6..5ffa9d7 100644 --- a/tests/integration/__snapshots__/integration.test.ts.snap +++ b/tests/integration/__snapshots__/integration.test.ts.snap @@ -10,23 +10,24 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8111 [chromium] accessibility advanced 1) Branding + 2) Multiple Parts autoTitles ✔ No Failures delays ✔ Short Delay And Pass - 2) Short Delay And Fail + 3) Short Delay And Fail ✔ Medium Delay And Pass - 3) Medium Delay And Fail - 4) Medium Delay And Short Timeout Fail - 5) Long Delay And Timeout + 4) Medium Delay And Fail + 5) Medium Delay And Short Timeout Fail + 6) Long Delay And Timeout simple ✔ No Failures - 6) Failure No Discernible Text - 7) Failure Color Contrast - 8) Failure No Discernible Text And Invalid Role + 7) Failure No Discernible Text + 8) Failure Color Contrast + 9) Failure No Discernible Text And Invalid Role - Failure Color Contrast Skipped - No Failures Warn - Failure Color Contrast Warn @@ -34,7 +35,7 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8111 - Failure Color Contrast Off - Failure No Discernible Text And Invalid Role Skipped ✔ Failure Color Contrast Disabled Rule - 9) Failure No Discernible Text And Invalid Role Disabled One Rule + 10) Failure No Discernible Text And Invalid Role Disabled One Rule ✔ Failure No Discernible Text And Invalid Role Disabled Rules 2 violations were detected in stories with "mode" set to "warn", so did not fail the test suite: @@ -69,7 +70,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access 6 passing 6 pending - 9 failing + 10 failing 1) advanced Branding: @@ -86,7 +87,22 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 - 2) delays + 2) advanced + Multiple Parts: + Detected the following accessibility violations! + + 1. color-contrast (Elements must meet minimum color contrast ratio thresholds) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 + + + 3) delays Short Delay And Fail: Detected the following accessibility violations! @@ -101,7 +117,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Role must be one of the valid ARIA roles: wut-the-wut - 3) delays + 4) delays Medium Delay And Fail: Detected the following accessibility violations! @@ -116,19 +132,19 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Role must be one of the valid ARIA roles: wut-the-wut - 4) delays + 5) delays Medium Delay And Short Timeout Fail: Error: Timeout of 100ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. at listOnTimeout (node:internal/timers) at processTimers (node:internal/timers) - 5) delays + 6) delays Long Delay And Timeout: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. at listOnTimeout (node:internal/timers) at processTimers (node:internal/timers) - 6) simple + 7) simple Failure No Discernible Text: Detected the following accessibility violations! @@ -147,7 +163,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element's default semantics were not overridden with role="none" or role="presentation" - 7) simple + 8) simple Failure Color Contrast: Detected the following accessibility violations! @@ -162,7 +178,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 - 8) simple + 9) simple Failure No Discernible Text And Invalid Role: Detected the following accessibility violations! @@ -191,7 +207,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element's default semantics were not overridden with role="none" or role="presentation" - 9) simple + 10) simple Failure No Discernible Text And Invalid Role Disabled One Rule: Detected the following accessibility violations! @@ -225,6 +241,7 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8000 [chromium] accessibility advanced ✔ Branding + ✔ Multiple Parts autoTitles ✔ No Failures @@ -270,7 +287,7 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8000 Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation" - 8 passing + 9 passing 6 pending 7 failing @@ -398,6 +415,7 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8000 [chromium] accessibility advanced - Branding + - Multiple Parts autoTitles - No Failures @@ -456,7 +474,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element's default semantics were not overridden with role="none" or role="presentation" 3 passing - 14 pending + 15 pending 4 failing 1) simple @@ -556,23 +574,24 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8000 [chromium] accessibility advanced 1) Branding + 2) Multiple Parts autoTitles ✔ No Failures delays ✔ Short Delay And Pass - 2) Short Delay And Fail + 3) Short Delay And Fail ✔ Medium Delay And Pass - 3) Medium Delay And Fail - 4) Medium Delay And Short Timeout Fail - 5) Long Delay And Timeout + 4) Medium Delay And Fail + 5) Medium Delay And Short Timeout Fail + 6) Long Delay And Timeout simple ✔ No Failures - 6) Failure No Discernible Text - 7) Failure Color Contrast - 8) Failure No Discernible Text And Invalid Role + 7) Failure No Discernible Text + 8) Failure Color Contrast + 9) Failure No Discernible Text And Invalid Role - Failure Color Contrast Skipped - No Failures Warn - Failure Color Contrast Warn @@ -580,7 +599,7 @@ Serving up 🍕 static storybook build at: http://127.0.0.1:8000 - Failure Color Contrast Off - Failure No Discernible Text And Invalid Role Skipped ✔ Failure Color Contrast Disabled Rule - 9) Failure No Discernible Text And Invalid Role Disabled One Rule + 10) Failure No Discernible Text And Invalid Role Disabled One Rule ✔ Failure No Discernible Text And Invalid Role Disabled Rules 2 violations were detected in stories with "mode" set to "warn", so did not fail the test suite: @@ -615,7 +634,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access 6 passing 6 pending - 9 failing + 10 failing 1) advanced Branding: @@ -632,7 +651,22 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 - 2) delays + 2) advanced + Multiple Parts: + Detected the following accessibility violations! + + 1. color-contrast (Elements must meet minimum color contrast ratio thresholds) + + For more info, visit https://dequeuniversity.com/rules/axe/4.9/color-contrast?application=axeAPI. + + Check these nodes: + + - html: + summary: Fix any of the following: + Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 + + + 3) delays Short Delay And Fail: Detected the following accessibility violations! @@ -647,7 +681,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Role must be one of the valid ARIA roles: wut-the-wut - 3) delays + 4) delays Medium Delay And Fail: Detected the following accessibility violations! @@ -662,19 +696,19 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Role must be one of the valid ARIA roles: wut-the-wut - 4) delays + 5) delays Medium Delay And Short Timeout Fail: Error: Timeout of 100ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. at listOnTimeout (node:internal/timers) at processTimers (node:internal/timers) - 5) delays + 6) delays Long Delay And Timeout: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. at listOnTimeout (node:internal/timers) at processTimers (node:internal/timers) - 6) simple + 7) simple Failure No Discernible Text: Detected the following accessibility violations! @@ -693,7 +727,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element's default semantics were not overridden with role="none" or role="presentation" - 7) simple + 8) simple Failure Color Contrast: Detected the following accessibility violations! @@ -708,7 +742,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element has insufficient color contrast of 1.51 (foreground color: #ff69b4, background color: #ff0000, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1 - 8) simple + 9) simple Failure No Discernible Text And Invalid Role: Detected the following accessibility violations! @@ -737,7 +771,7 @@ Error: simple / Failure No Discernible Text Warn / Detected the following access Element's default semantics were not overridden with role="none" or role="presentation" - 9) simple + 10) simple Failure No Discernible Text And Invalid Role Disabled One Rule: Detected the following accessibility violations!