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

feat(context): allow selecting shadow DOM nodes #3798

Merged
merged 15 commits into from
Dec 1, 2022
56 changes: 41 additions & 15 deletions axe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,48 @@ declare namespace axe {
| 'embedded'
| 'interactive';

// Array of length 2 or greater
type MultiArray<T> = [T, T, ...T[]];

// Selectors within a frame
type BaseSelector = string;
type CrossTreeSelector = BaseSelector | BaseSelector[];
type CrossFrameSelector = CrossTreeSelector[];

type ContextObject = {
include?: Node | BaseSelector | Array<Node | BaseSelector | BaseSelector[]>;
exclude?: Node | BaseSelector | Array<Node | BaseSelector | BaseSelector[]>;
};
type ShadowDomSelector = MultiArray<BaseSelector>;
type CrossTreeSelector = BaseSelector | ShadowDomSelector;
type LabelledShadowDomSelector = { fromShadowDom: ShadowDomSelector };

type SerialContextObject = {
include?: BaseSelector | Array<BaseSelector | BaseSelector[]>;
exclude?: BaseSelector | Array<BaseSelector | BaseSelector[]>;
};
// Cross-frame selectors
type FramesSelector = Array<CrossTreeSelector | LabelledShadowDomSelector>;
type UnlabelledFrameSelector = CrossTreeSelector[];
type LabelledFramesSelector = { fromFrames: MultiArray<FramesSelector[0]> };
/**
* @deprecated Use UnlabelledFrameSelector instead
*/
type CrossFrameSelector = UnlabelledFrameSelector;

type RunCallback = (error: Error, results: AxeResults) => void;
// Context options
type Selector =
| Node
| BaseSelector
| LabelledShadowDomSelector
| LabelledFramesSelector;
type SelectorList = Array<Selector | FramesSelector> | NodeList;
type ContextObject =
| {
include: Selector | SelectorList;
exclude?: Selector | SelectorList;
}
| {
exclude: Selector | SelectorList;
};
type ElementContext = Selector | SelectorList | ContextObject;

interface SerialContextObject {
include: UnlabelledFrameSelector[];
exclude: UnlabelledFrameSelector[];
}

type ElementContext = Node | NodeList | string | ContextObject;
type RunCallback = (error: Error, results: AxeResults) => void;

interface TestEngine {
name: string;
Expand Down Expand Up @@ -255,9 +280,9 @@ declare namespace axe {
interface SerialDqElement {
source: string;
nodeIndexes: number[];
selector: CrossFrameSelector;
selector: UnlabelledFrameSelector;
xpath: string[];
ancestry: CrossFrameSelector;
ancestry: UnlabelledFrameSelector;
}
interface PartialRuleResult {
id: string;
Expand All @@ -273,7 +298,7 @@ declare namespace axe {
}
type PartialResults = Array<PartialResult | null>;
interface FrameContext {
frameSelector: CrossTreeSelector;
frameSelector: UnlabelledFrameSelector;
frameContext: SerialContextObject;
}
interface Utils {
Expand All @@ -282,6 +307,7 @@ declare namespace axe {
options?: RunOptions
) => FrameContext[];
shadowSelect: (selector: CrossTreeSelector) => Element | null;
shadowSelectAll: (selector: CrossTreeSelector) => Element[];
}
interface EnvironmentData {
testEngine: TestEngine;
Expand Down
76 changes: 17 additions & 59 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,106 +321,64 @@ By default, `axe.run` will test the entire document. The context object is an op
- Example: To limit analysis to the `<div id="content">` element: `document.getElementById("content")`
1. A NodeList such as returned by `document.querySelectorAll`.
1. A [CSS selector](./developer-guide.md#supported-css-selectors) that selects the portion(s) of the document that must be analyzed.
1. An include-exclude object (see below)
1. An object with `exclude` and/or `include` properties
1. An object with a `fromFrames` property
1. An object with a `fromShadowDom` property

###### Include-Exclude Object

The include exclude object is a JSON object with two attributes: include and exclude. Either include or exclude is required. If only `exclude` is specified; include will default to the entire `document`.

- A node, or
- An array of Nodes or an array of arrays of [CSS selectors](./developer-guide.md#supported-css-selectors)
- If the nested array contains a single string, that string is the CSS selector
- If the nested array contains multiple strings
- The last string is the final CSS selector
- All other's are the nested structure of iframes inside the document

In most cases, the component arrays will contain only one CSS selector. Multiple CSS selectors are only required if you want to include or exclude regions of a page that are inside iframes (or iframes within iframes within iframes). In this case, the first n-1 selectors are selectors that select the iframe(s) and the nth selector, selects the region(s) within the iframe.
Read [context.md](context.md) for details about the context object.

###### Context Parameter Examples

1. Include the first item in the `$fixture` NodeList but exclude its first child

```js
axe.run(
{
include: $fixture[0],
exclude: $fixture[0].firstChild
},
(err, results) => {
// ...
}
);
```

2. Include the element with the ID of `fix` but exclude any `div`s within it
1. Test the `#navBar` and all other `nav` elements and its content.

```js
axe.run(
{
include: [['#fix']],
exclude: [['#fix div']]
},
(err, results) => {
// ...
}
);
axe.run([`#navBar`, `nav`], (err, results) => {
// ...
});
```

3. Include the whole document except any structures whose parent contains the class `exclude1` or `exclude2`
2. Test everything except `.ad-banner` elements.

```js
axe.run(
{
exclude: [['.exclude1'], ['.exclude2']]
exclude: '.ad-banner'
},
(err, results) => {
// ...
}
);
```

4. Include the element with the ID of `fix`, within the iframe with id `frame`
3. Test the `form` element inside the `#payment` iframe.

```js
axe.run(
{
include: [['#frame', '#fix']]
fromFrames: ['iframe#payment', 'form']
},
(err, results) => {
// ...
}
);
```

5. Include the element with the ID of `fix`, within the iframe with id `frame2`, within the iframe with id `frame1`
4. Exclude all `.commentBody` elements in each `.commentsShadowHost` shadow DOM tree.

```js
axe.run(
{
include: [['#frame1', '#frame2', '#fix']]
exclude: {
fromShadowDom: ['.commentsShadowHost', '.commentBody']
}
},
(err, results) => {
// ...
}
);
```

6. Include the following:

- The element with the ID of `fix`, within the iframe with id `frame2`, within the iframe with id `frame1`
- The element with id `header`
- All links

```js
axe.run(
{
include: [['#header'], ['a'], ['#frame1', '#frame2', '#fix']]
},
(err, results) => {
// ...
}
);
```
More details on how to use the context object are described in [context.md](context.md).

##### Options Parameter

Expand Down
Loading