Skip to content

Commit 9e1e31b

Browse files
WilcoFiersstrakerenlarsen
authored
feat(context): allow selecting shadow DOM nodes (#3798)
* feat(context): allow scoping shadow DOM nodes * Refactor Context code * Code complete * cleanup * Fix tests * Update type definition * Write docs * Apply suggestions from code review Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> * Resolve comments * Add type definition tests * Apply suggestions from code review Co-authored-by: Erik Larsen <enlarsen@users.noreply.github.com> Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> * Update context.md * fix test * Update type test Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> Co-authored-by: Erik Larsen <enlarsen@users.noreply.github.com>
1 parent 5026d65 commit 9e1e31b

22 files changed

Lines changed: 1630 additions & 658 deletions

axe.d.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,48 @@ declare namespace axe {
4545
| 'embedded'
4646
| 'interactive';
4747

48+
// Array of length 2 or greater
49+
type MultiArray<T> = [T, T, ...T[]];
50+
51+
// Selectors within a frame
4852
type BaseSelector = string;
49-
type CrossTreeSelector = BaseSelector | BaseSelector[];
50-
type CrossFrameSelector = CrossTreeSelector[];
5153

52-
type ContextObject = {
53-
include?: Node | BaseSelector | Array<Node | BaseSelector | BaseSelector[]>;
54-
exclude?: Node | BaseSelector | Array<Node | BaseSelector | BaseSelector[]>;
55-
};
54+
type ShadowDomSelector = MultiArray<BaseSelector>;
55+
type CrossTreeSelector = BaseSelector | ShadowDomSelector;
56+
type LabelledShadowDomSelector = { fromShadowDom: ShadowDomSelector };
5657

57-
type SerialContextObject = {
58-
include?: BaseSelector | Array<BaseSelector | BaseSelector[]>;
59-
exclude?: BaseSelector | Array<BaseSelector | BaseSelector[]>;
60-
};
58+
// Cross-frame selectors
59+
type FramesSelector = Array<CrossTreeSelector | LabelledShadowDomSelector>;
60+
type UnlabelledFrameSelector = CrossTreeSelector[];
61+
type LabelledFramesSelector = { fromFrames: MultiArray<FramesSelector[0]> };
62+
/**
63+
* @deprecated Use UnlabelledFrameSelector instead
64+
*/
65+
type CrossFrameSelector = UnlabelledFrameSelector;
6166

62-
type RunCallback = (error: Error, results: AxeResults) => void;
67+
// Context options
68+
type Selector =
69+
| Node
70+
| BaseSelector
71+
| LabelledShadowDomSelector
72+
| LabelledFramesSelector;
73+
type SelectorList = Array<Selector | FramesSelector> | NodeList;
74+
type ContextObject =
75+
| {
76+
include: Selector | SelectorList;
77+
exclude?: Selector | SelectorList;
78+
}
79+
| {
80+
exclude: Selector | SelectorList;
81+
};
82+
type ElementContext = Selector | SelectorList | ContextObject;
83+
84+
interface SerialContextObject {
85+
include: UnlabelledFrameSelector[];
86+
exclude: UnlabelledFrameSelector[];
87+
}
6388

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

6691
interface TestEngine {
6792
name: string;
@@ -255,9 +280,9 @@ declare namespace axe {
255280
interface SerialDqElement {
256281
source: string;
257282
nodeIndexes: number[];
258-
selector: CrossFrameSelector;
283+
selector: UnlabelledFrameSelector;
259284
xpath: string[];
260-
ancestry: CrossFrameSelector;
285+
ancestry: UnlabelledFrameSelector;
261286
}
262287
interface PartialRuleResult {
263288
id: string;
@@ -273,7 +298,7 @@ declare namespace axe {
273298
}
274299
type PartialResults = Array<PartialResult | null>;
275300
interface FrameContext {
276-
frameSelector: CrossTreeSelector;
301+
frameSelector: UnlabelledFrameSelector;
277302
frameContext: SerialContextObject;
278303
}
279304
interface Utils {
@@ -282,6 +307,7 @@ declare namespace axe {
282307
options?: RunOptions
283308
) => FrameContext[];
284309
shadowSelect: (selector: CrossTreeSelector) => Element | null;
310+
shadowSelectAll: (selector: CrossTreeSelector) => Element[];
285311
}
286312
interface EnvironmentData {
287313
testEngine: TestEngine;

doc/API.md

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -321,106 +321,64 @@ By default, `axe.run` will test the entire document. The context object is an op
321321
- Example: To limit analysis to the `<div id="content">` element: `document.getElementById("content")`
322322
1. A NodeList such as returned by `document.querySelectorAll`.
323323
1. A [CSS selector](./developer-guide.md#supported-css-selectors) that selects the portion(s) of the document that must be analyzed.
324-
1. An include-exclude object (see below)
324+
1. An object with `exclude` and/or `include` properties
325+
1. An object with a `fromFrames` property
326+
1. An object with a `fromShadowDom` property
325327

326-
###### Include-Exclude Object
327-
328-
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`.
329-
330-
- A node, or
331-
- An array of Nodes or an array of arrays of [CSS selectors](./developer-guide.md#supported-css-selectors)
332-
- If the nested array contains a single string, that string is the CSS selector
333-
- If the nested array contains multiple strings
334-
- The last string is the final CSS selector
335-
- All other's are the nested structure of iframes inside the document
336-
337-
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.
328+
Read [context.md](context.md) for details about the context object.
338329

339330
###### Context Parameter Examples
340331

341-
1. Include the first item in the `$fixture` NodeList but exclude its first child
342-
343-
```js
344-
axe.run(
345-
{
346-
include: $fixture[0],
347-
exclude: $fixture[0].firstChild
348-
},
349-
(err, results) => {
350-
// ...
351-
}
352-
);
353-
```
354-
355-
2. Include the element with the ID of `fix` but exclude any `div`s within it
332+
1. Test the `#navBar` and all other `nav` elements and its content.
356333

357334
```js
358-
axe.run(
359-
{
360-
include: [['#fix']],
361-
exclude: [['#fix div']]
362-
},
363-
(err, results) => {
364-
// ...
365-
}
366-
);
335+
axe.run([`#navBar`, `nav`], (err, results) => {
336+
// ...
337+
});
367338
```
368339

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

371342
```js
372343
axe.run(
373344
{
374-
exclude: [['.exclude1'], ['.exclude2']]
345+
exclude: '.ad-banner'
375346
},
376347
(err, results) => {
377348
// ...
378349
}
379350
);
380351
```
381352

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

384355
```js
385356
axe.run(
386357
{
387-
include: [['#frame', '#fix']]
358+
fromFrames: ['iframe#payment', 'form']
388359
},
389360
(err, results) => {
390361
// ...
391362
}
392363
);
393364
```
394365

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

397368
```js
398369
axe.run(
399370
{
400-
include: [['#frame1', '#frame2', '#fix']]
371+
exclude: {
372+
fromShadowDom: ['.commentsShadowHost', '.commentBody']
373+
}
401374
},
402375
(err, results) => {
403376
// ...
404377
}
405378
);
406379
```
407380

408-
6. Include the following:
409-
410-
- The element with the ID of `fix`, within the iframe with id `frame2`, within the iframe with id `frame1`
411-
- The element with id `header`
412-
- All links
413-
414-
```js
415-
axe.run(
416-
{
417-
include: [['#header'], ['a'], ['#frame1', '#frame2', '#fix']]
418-
},
419-
(err, results) => {
420-
// ...
421-
}
422-
);
423-
```
381+
More details on how to use the context object are described in [context.md](context.md).
424382

425383
##### Options Parameter
426384

0 commit comments

Comments
 (0)