Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axe context #98

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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: {
Expand All @@ -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).
Expand Down
23 changes: 23 additions & 0 deletions demo/src/advanced.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,26 @@ export const branding = {
},
},
};

// Test out passing context to `axe.run`.
export const multipleParts = {
render: () => (
<div>
<div id="a">
<button style={{backgroundColor: 'red', color: 'hotpink'}}>
hello A
</button>
</div>
<div id="b">
<button style={{backgroundColor: 'red', color: 'hotpink'}}>
hello B
</button>
</div>
</div>
),
parameters: {
axe: {
context: '#a',
},
},
};
12 changes: 11 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type {RunOptions, Spec} from 'axe-core';
import type {
RunOptions,
SerialContextObject,
SerialFrameSelector,
Spec,
} from 'axe-core';

export declare type AxeParams = {
/**
Expand Down Expand Up @@ -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`.
*/
Expand Down
29 changes: 29 additions & 0 deletions src/ProcessedStory.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -42,6 +44,9 @@ export default class ProcessedStory {
rawStory.parameters?.axe?.runOptions,
rawStory,
),
context: normalizeContext(rawStory.parameters?.axe?.context, rawStory) as
| Context
| undefined,
Comment on lines +47 to +49
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note the as. I didn't feel like creating a Zod schema for all the permutations the context object can take, so left it just checking the general shape.

config: normalizeConfig(rawStory.parameters?.axe?.config, rawStory),
};
}
Expand Down Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if this is checked elsewhere, but is .parameters guaranteed to exist here? (assuming typescript would be complaining if not)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's initialized in this object's constructor

}

/**
* 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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/Result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class Result {
page,
disabledRules,
story.runOptions,
story.context,
story.config,
);
return new Result(axeResults.violations);
Expand Down
24 changes: 20 additions & 4 deletions src/browser/AxePage.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand All @@ -17,20 +29,24 @@ export function analyze(
page: Page,
disabledRules: string[] = [],
runOptions: RunOptions = {},
context?: Context,
config?: Spec,
): Promise<AxeResults> {
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<AxeResults> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This function executes in a browser context.
Expand All @@ -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);
});
}

Expand Down
Loading
Loading