automatica11y 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/README.md +39 -122
- package/package.json +28 -2
- package/skills/automatica11y/SKILL.md +3 -1
- package/skills/automatica11y-runner/SKILL.md +19 -4
- package/skills/automatica11y-runner/references/fixtures.md +22 -0
- package/src/cli.js +1 -0
- package/src/commands/common.js +14 -11
- package/src/frameworks/angular-errors.js +27 -0
- package/src/frameworks/angular-selectors.js +36 -0
- package/src/frameworks/angular.js +156 -0
- package/src/frameworks/html.js +163 -0
- package/src/frameworks/index.js +12 -7
- package/src/frameworks/react.js +2 -1
- package/src/frameworks/vue.js +2 -1
- package/src/globals.d.ts +123 -6
- package/src/harness/bundle.js +2 -1
- package/src/harness/generate/angular-recipes.js +360 -0
- package/src/harness/generate/probe.js +16 -5
- package/src/harness/npm-install.js +43 -11
- package/src/harness/shadow.js +1 -1
- package/src/harness/storybook.js +18 -5
- package/src/plan/build-plan.js +1 -0
- package/src/plan/classify.js +39 -11
- package/src/plan/mapping.js +5 -5
- package/src/plan/resolve-npm.js +47 -17
- package/src/plan/subpath.js +17 -0
- package/src/report/comparison.js +4 -1
- package/src/report/index.js +1 -1
- package/src/report/parts.js +13 -1
- package/src/report/single.js +1 -1
- package/src/run/audit-npm.js +93 -26
- package/src/run/fail-check.js +10 -2
- package/src/run/generate-fixture.js +5 -4
- package/src/run/run-plan.js +8 -7
- package/src/run/summary.js +1 -1
- package/src/schema.js +9 -2
- package/src/tiers/computed/checks.js +3 -1
- package/src/tiers/conditions/kit.js +3 -3
- package/src/tiers/interactions/archetypes.js +6 -2
- package/src/tiers/interactions/focus-indicator.js +4 -2
- package/src/tiers/interactions/helpers.js +2 -1
- package/src/tiers/rules/ibm.js +2 -2
- package/src/tiers/rules/index.js +1 -1
package/AGENTS.md
CHANGED
|
@@ -10,6 +10,8 @@ Keep the user's request as they gave it, including the target and any settings t
|
|
|
10
10
|
npx --yes automatica11y@latest guide skill
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
If `automatica11y` is already on the `PATH` (the user ran `npm i -g automatica11y`, and `automatica11y --version` prints a version), you can run `automatica11y guide skill` and `automatica11y guide fixtures` instead, and use `automatica11y` wherever the steps say `npx --yes automatica11y@latest`. The version check in the steps still applies. An installed copy can be older than the steps expect, and then the check stops you. Use `npx` when the installed version is the wrong series, or when nothing is installed.
|
|
14
|
+
|
|
13
15
|
When the steps tell you to write a fixture, read the fixture guide:
|
|
14
16
|
|
|
15
17
|
```bash
|
package/README.md
CHANGED
|
@@ -1,161 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://automatica11y.dev/)
|
|
2
|
+
|
|
3
|
+
# automatica11y
|
|
2
4
|
|
|
3
5
|
Test and compare the accessibility of web pages, Storybook builds, and npm component libraries.
|
|
4
6
|
|
|
5
|
-
automatica11y
|
|
7
|
+
automatica11y helps answer two questions:
|
|
6
8
|
|
|
7
9
|
- How accessible is this? (`audit`)
|
|
8
|
-
- How do these compare? (`compare
|
|
9
|
-
|
|
10
|
-
It's **not** an attestation or certification tool. Automated checks cover only part of WCAG, so a report says "no automated violations found" only where an engine found none, and never says "accessible." See [Limits](#limits).
|
|
10
|
+
- How do these compare? (`compare`)
|
|
11
11
|
|
|
12
|
-
## Quick start
|
|
12
|
+
## Quick start
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Run it from your terminal:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx automatica11y
|
|
18
|
-
npx automatica11y audit https://example.com
|
|
19
|
-
npx automatica11y compare radix=npm:@radix-ui/react-dialog aria=npm:react-aria-components
|
|
17
|
+
npx -y automatica11y audit npm:@instructure/ui-buttons
|
|
20
18
|
```
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
Install the [skill](skills/automatica11y/SKILL.md) and use it with an agent:
|
|
23
21
|
|
|
24
|
-
```
|
|
25
|
-
|
|
22
|
+
```text
|
|
23
|
+
/automatica11y compare "example.com/a.html" "example.com/b.html"
|
|
26
24
|
```
|
|
25
|
+
Add it to your CD/CI pipeline:
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
A target is `[label=]<spec>`. The label is optional, and names the target in the report.
|
|
33
|
-
|
|
34
|
-
| Target | Spec |
|
|
35
|
-
|---|---|
|
|
36
|
-
| A live page | `https://example.com/page` |
|
|
37
|
-
| A local page or site | `./page.html` or `./dist`. A path with no prefix is relative to the working folder, so `dist` means `./dist`. Prefixes `../`, `/`, `~`, and `file:` work too. |
|
|
38
|
-
| A Storybook | Its URL, or a local folder with `index.json` or `stories.json`. |
|
|
39
|
-
| An npm package | `npm:name`, `npm:@scope/name`, or `npm:name@version`. Add a sub-path to test one entry of a package: `npm:@scope/pkg/button` or `npm:@scope/pkg@1.2.3/button/v2`. React, Vue 3, and web component libraries work. |
|
|
40
|
-
|
|
41
|
-
A bare word such as `button` is a path: the folder or file `./button`. Write `npm:button` to pick the package. The prefix is what chooses a package, so a folder with the same name never gets in the way. A bare word that isn't a path fails with a hint, such as "If you meant the npm package, write npm:react."
|
|
42
|
-
|
|
43
|
-
A local `.html` file is served over `http://localhost`, never `file://`. A static site audits its `index.html` only.
|
|
44
|
-
|
|
45
|
-
## What it checks.
|
|
46
|
-
|
|
47
|
-
Five tiers run by default. Use `--tiers` to pick fewer.
|
|
48
|
-
|
|
49
|
-
- **Rules.** axe-core and IBM Equal Access run side by side. They overlap, and each catches things the other misses. Their findings are reported separately and never added together. axe-core reports an impact (`minor` to `critical`). IBM reports its Toolkit level, a staged adoption scale where level 1 is essential, high-impact requirements. The two scales aren't comparable.
|
|
50
|
-
- **Interactions.** Keyboard and focus checks for ten archetypes: button, link, dialog, menu, tabs, combobox, form-field, accordion, tooltip, and live-region (a message that appears or changes without moving focus, such as an alert or status message). Each check runs on a fresh page.
|
|
51
|
-
- **Computed checks.** automatica11y's own measurements from resolved styles in the browser, for the trigger of each archetype fixture: text contrast in rest, hover, keyboard focus, and pressed states (1.4.3), the contrast of the control's edge, fill, or icon (1.4.11), and the contrast and thickness of the focus indicator (1.4.11, and 2.4.13 at level AAA). A page that uses a gradient, an image, or transparency behind the control can't be reduced to one color, so that check reports `undetermined`, which is a gap and never a pass. These results are reported on their own and never added to the rule engines' counts.
|
|
52
|
-
- **Conditions.** How the page holds up under a user's settings and environment. Each check opens fresh copies of the page and says what happened:
|
|
53
|
-
- `prefers-reduced-motion: reduce` (2.3.3, 2.2.2): do animations that move or repeat stop or go away? Motion driven by JavaScript timers isn't visible to this check.
|
|
54
|
-
- `prefers-color-scheme: dark` (1.4.3): if the page changes in dark mode, does every piece of text keep its contrast? A page that doesn't adapt isn't failed.
|
|
55
|
-
- `prefers-contrast: more` (1.4.3, 1.4.6): if the page responds, does the lowest text contrast stay above the minimum and not drop? The check says whether enhanced contrast (7:1) is reached. `prefers-contrast: less` (1.4.3): if the page softens, does text stay above the minimum? A page that doesn't respond isn't failed.
|
|
56
|
-
- `prefers-reduced-transparency: reduce` (1.4.3, 1.4.11): do surfaces that hold text stop being see-through (translucent backgrounds, backdrop blur)? This preference isn't a WCAG requirement. It matters because see-through backgrounds make text contrast unpredictable.
|
|
57
|
-
- Forced colors (1.4.11, 2.4.7): is the focus indicator still visible? A ring drawn with `box-shadow` disappears in forced colors, so use an outline.
|
|
58
|
-
- Reflow at 320 CSS pixels (1.4.10): does the page scroll sideways, or does anything reach past the right edge? Two-dimensional content such as data tables and maps is exempt, so a person judges those.
|
|
59
|
-
- Text spacing (1.4.12): with the spacing the criterion names, does any element cut off its text? Overlapping text isn't checked.
|
|
60
|
-
|
|
61
|
-
Conditions run on whole pages and on component fixtures. Storybook stories are reported as not applicable.
|
|
62
|
-
- **Virtual screen reader.** The announcements a simulated screen reader makes, recorded as data. The output is simulated. It isn't a real screen reader, and real ones announce things differently.
|
|
63
|
-
|
|
64
|
-
Every result says what it ran, or why it didn't. A gap, a failure, or a result that can't be tested is a finding. It never counts as a pass.
|
|
65
|
-
|
|
66
|
-
## Options.
|
|
67
|
-
|
|
68
|
-
| Option | Default | What it does |
|
|
69
|
-
|---|---|---|
|
|
70
|
-
| `--wcag 2.0\|2.1\|2.2` | `2.2` | The WCAG version. |
|
|
71
|
-
| `--level A\|AA\|AAA` | `AA` | The conformance level. IBM Equal Access has no AAA rules, so it runs its AA rules and says so. |
|
|
72
|
-
| `--engine axe,ibm` | both | Which rule engines run. |
|
|
73
|
-
| `--tiers rules,interactions,computed,conditions,vsr` | all | Which tiers run. |
|
|
74
|
-
| `--archetypes a,b` | all | Limit npm and Storybook targets to these archetypes. |
|
|
75
|
-
| `--lib-a11y on,off` | both | For libraries with opt-in accessibility features. See [the fixture guide](skills/automatica11y-runner/references/fixtures.md). |
|
|
76
|
-
| `--mapping <file>` | none | A mapping file for npm targets. |
|
|
77
|
-
| `--no-generate` | off | Don't build fixtures for npm packages. Only authored fixtures and the `button` and `link` templates run. |
|
|
78
|
-
| `--max-stories <n>` | `200` | The Storybook story cap. The cap spreads over components. |
|
|
79
|
-
| `--out <dir>` | `./a11y-report` | Where results go. |
|
|
80
|
-
| `--plan` | off | Classify the targets and write `plan.json`, then stop. Nothing installs and no browser launches. |
|
|
81
|
-
| `--fail-on-axe <impact>` | off | Exit 1 if axe-core reports a violation at or above `minor`, `moderate`, `serious`, or `critical`. |
|
|
82
|
-
| `--fail-on-ibm <1\|2\|3>` | off | Exit 1 if IBM reports a violation at or below that Toolkit level. |
|
|
83
|
-
| `--fail-mode any\|all` | `any` | `any` trips when either engine's check trips. `all` trips only when both do, on the same target. |
|
|
84
|
-
|
|
85
|
-
Run `automatica11y run --plan <plan.json>` to repeat a saved plan. It warns if tool versions have changed.
|
|
86
|
-
|
|
87
|
-
## Output.
|
|
88
|
-
|
|
89
|
-
- `report.md` is a complete report, written without any model. It opens with a coverage matrix, then lists findings, then ends with a method note.
|
|
90
|
-
- `results.json` holds everything, including element selectors and announcement logs.
|
|
91
|
-
- `plan.json` records what ran and the resolved tool and package versions.
|
|
92
|
-
- `mapping.json` appears for npm targets. See below.
|
|
93
|
-
|
|
94
|
-
## Exit codes.
|
|
95
|
-
|
|
96
|
-
| Code | Meaning |
|
|
97
|
-
|---|---|
|
|
98
|
-
| 0 | The run completed. Findings don't change this unless a fail flag is set. |
|
|
99
|
-
| 1 | The run completed and a fail check tripped. |
|
|
100
|
-
| 2 | The command line was wrong. |
|
|
101
|
-
| 3 | An environment problem, such as a missing browser or an old Node. The message says how to fix it. |
|
|
102
|
-
| 4 | No target produced results. |
|
|
27
|
+
```bash
|
|
28
|
+
automatica11y audit ./dist --fail-on-axe serious
|
|
29
|
+
```
|
|
103
30
|
|
|
104
|
-
|
|
31
|
+
**[Read the documentation](https://automatica11y.dev/)**
|
|
105
32
|
|
|
106
|
-
##
|
|
33
|
+
## Why use it
|
|
107
34
|
|
|
108
|
-
|
|
35
|
+
Most accessibility tools scan one page and hand back one score. automatica11y is built for the questions that come before and after that scan.
|
|
109
36
|
|
|
110
|
-
|
|
37
|
+
### It compares
|
|
111
38
|
|
|
112
|
-
|
|
113
|
-
2. **Template.** The tool builds `button` and `link` tests from the export name alone.
|
|
114
|
-
3. **Generated.** For dialog, menu, tooltip, tabs, accordion, combobox, form-field, and live-region, the tool builds candidate fixtures from what the package exports. It looks for compound parts by common names (a root, a trigger, a content part, a title, a close part, and so on), either as `Dialog.Root` or as `DialogRoot`, and it tries the usual ways of switching a component on (an `open` prop and a close handler). For web components it reads what each element says about itself: its observed attributes, its members, and its slots. It then bundles each candidate, loads it, and checks that exactly one element is the trigger, that nothing logged an error, that activating the trigger shows a root, and that the root carries a role that fits. The first candidate that passes is used. If none does, the archetype is a gap, and the report lists what was tried and why each attempt failed.
|
|
115
|
-
4. **Gap.** Anything else is a gap in the report.
|
|
39
|
+
Pick two component libraries, two versions of one component, or two sites, and `compare` runs them under the same settings, side by side. "Is this library's dialog better than that one?" gets an answer you can check.
|
|
116
40
|
|
|
117
|
-
|
|
41
|
+
### It tests components, not just pages
|
|
118
42
|
|
|
119
|
-
|
|
43
|
+
Point it at an npm package. It installs the package on its own, finds the components, builds the test fixtures it needs (React, Vue 3, Angular 22 and newer, and web components), then opens the dialogs and menus and presses the keys.
|
|
120
44
|
|
|
121
|
-
|
|
45
|
+
### It's' composable
|
|
122
46
|
|
|
123
|
-
|
|
47
|
+
Out of the box it looks at standard accessibility interactions and archetypes (buttons, links, dialogs, contrast, etc.) and you can bring your own simple fixtures for more complex components.
|
|
124
48
|
|
|
125
|
-
|
|
126
|
-
npx automatica11y@latest guide # where to start (the AGENTS.md file)
|
|
127
|
-
npx automatica11y@latest guide skill # the full steps: build the command, run it, write the report
|
|
128
|
-
npx automatica11y@latest guide fixtures # how to write the fixtures an npm package needs
|
|
129
|
-
```
|
|
49
|
+
### An AI agent can run it
|
|
130
50
|
|
|
131
|
-
The
|
|
51
|
+
The tool prints its own instructions, so you can ask an agent "how accessible is Radix Dialog?" and it knows what to do.
|
|
132
52
|
|
|
133
|
-
|
|
53
|
+
## What a result looks like
|
|
134
54
|
|
|
135
|
-
|
|
55
|
+
automatica11y generates a machine-readable json findings report, and a human-readable markdown version. Here's an excerpt from a real run on the carousel in shadcn/ui:
|
|
136
56
|
|
|
137
|
-
|
|
57
|
+
| Check | Result | Detail |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `focus-indicator-contrast` | fail | The border has 2.58:1 against what's next to it (needs 3:1). |
|
|
60
|
+
| `forced-colors-focus-visible` | fail | With forced colors on, only four of 156 changed pixels reach 3:1 against their unfocused color. |
|
|
61
|
+
| `boundary-contrast` | pass | The strongest mark is its icon stroke, at 19.79:1 (needs 3:1). |
|
|
138
62
|
|
|
139
|
-
-
|
|
140
|
-
- Components are tested in the states a fixture shows. Dialogs, menus, tooltips, and comboboxes run closed and open, and live regions run before and after the message. Other states aren't visited.
|
|
141
|
-
- Content on a canvas with no alternative, or inside a closed shadow root, can't be tested, and the report says so. The virtual screen reader can't read inside shadow roots at all.
|
|
142
|
-
- Svelte, Angular, Vue 2, and other frameworks report "unsupported framework." Their Storybooks work, because a Storybook renders stories in a page whatever the framework.
|
|
143
|
-
- Native screen readers aren't part of this version.
|
|
144
|
-
- Results are a snapshot. The tools run at their latest versions, and the report records them.
|
|
63
|
+
> axe-core found no automated violations on that component. IBM Equal Access found one: the carousel's region has no label.
|
|
145
64
|
|
|
146
|
-
##
|
|
65
|
+
## Limits
|
|
147
66
|
|
|
148
|
-
|
|
149
|
-
npx automatica11y audit ./dist --fail-on-axe serious --fail-on-ibm 1 --fail-mode any
|
|
150
|
-
```
|
|
67
|
+
automatica11y is not an attestation or certification tool. Automated checks cover only part of WCAG. They can't judge whether alt text is meaningful, whether the reading order makes sense, or how real screen readers behave, and the screen reader it runs is simulated. A person has to check those.
|
|
151
68
|
|
|
152
|
-
|
|
69
|
+
A report says "no automated violations found" where an engine found none, and never says "accessible." A gap, an error, or a result that isn't testable is a finding, not a pass.
|
|
153
70
|
|
|
154
|
-
## License
|
|
71
|
+
## License
|
|
155
72
|
|
|
156
73
|
MIT. See [LICENSE](LICENSE). The WCAG data below has its own terms.
|
|
157
74
|
|
|
158
|
-
## Attribution
|
|
75
|
+
## Attribution
|
|
159
76
|
|
|
160
77
|
automatica11y reads WCAG criterion numbers, names, levels, and versions from the W3C's published JSON, [wcag.json](https://www.w3.org/WAI/WCAG22/wcag.json). The package ships that file in `src/data/` without changes.
|
|
161
78
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automatica11y",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Test and compare the accessibility of web pages, Storybook builds, and npm component libraries.",
|
|
5
|
+
"homepage": "https://automatica11y.dev",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/thedannywahl/automatica11y.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/thedannywahl/automatica11y/issues"
|
|
12
|
+
},
|
|
5
13
|
"license": "MIT",
|
|
6
14
|
"type": "module",
|
|
7
15
|
"bin": {
|
|
@@ -21,6 +29,8 @@
|
|
|
21
29
|
"test": "node --test test/*.test.js",
|
|
22
30
|
"lint": "tsc -p jsconfig.json",
|
|
23
31
|
"update-wcag": "node scripts/update-wcag.js",
|
|
32
|
+
"docs:build": "node scripts/build-docs.js",
|
|
33
|
+
"docs:preview": "node scripts/build-docs.js && npx --yes serve site",
|
|
24
34
|
"prepublishOnly": "npm run lint"
|
|
25
35
|
},
|
|
26
36
|
"dependencies": {
|
|
@@ -33,11 +43,27 @@
|
|
|
33
43
|
"valibot": "^1.5.0"
|
|
34
44
|
},
|
|
35
45
|
"devDependencies": {
|
|
46
|
+
"@angular/cdk": "^22.2.2",
|
|
47
|
+
"@angular/common": "^22.2.2",
|
|
48
|
+
"@angular/compiler": "^22.2.2",
|
|
49
|
+
"@angular/core": "^22.2.2",
|
|
50
|
+
"@angular/forms": "^22.2.2",
|
|
51
|
+
"@angular/material": "^22.2.2",
|
|
52
|
+
"@angular/platform-browser": "^22.2.2",
|
|
53
|
+
"@pantoken/components": "^2.0.2",
|
|
54
|
+
"@pantoken/css": "^0.4.3",
|
|
55
|
+
"@pantoken/plugin-custom-theme-colors": "^0.3.3",
|
|
56
|
+
"@shikijs/langs": "^4.5.0",
|
|
57
|
+
"@shikijs/themes": "^4.5.0",
|
|
36
58
|
"@types/node": "^26.6.4",
|
|
59
|
+
"marked": "^18.1.0",
|
|
37
60
|
"react": "^19.3.0",
|
|
38
61
|
"react-dom": "^19.3.0",
|
|
62
|
+
"rxjs": "^7.8.2",
|
|
63
|
+
"shiki": "^4.5.0",
|
|
39
64
|
"typescript": "^7.0.2",
|
|
40
|
-
"vue": "^3.5.43"
|
|
65
|
+
"vue": "^3.5.43",
|
|
66
|
+
"zone.js": "^0.16.3"
|
|
41
67
|
},
|
|
42
68
|
"publishConfig": {
|
|
43
69
|
"access": "public"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: automatica11y
|
|
3
|
-
compatibility: Needs Node 20 or newer, a shell that can run npx, and network access to the npm registry.
|
|
3
|
+
compatibility: Needs Node 20 or newer, a shell that can run npx or the installed automatica11y command, and network access to the npm registry.
|
|
4
4
|
description: Test and compare web accessibility. Use when someone asks how accessible a web page, Storybook, or npm component library is, asks for an accessibility or WCAG check or audit, or asks to compare the accessibility of two or more sites or component libraries (for example "how accessible is Radix Dialog?" or "compare React Aria and Headless UI").
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -20,6 +20,8 @@ automatica11y tests and compares web accessibility. This skill only gets you sta
|
|
|
20
20
|
|
|
21
21
|
If the command exits with an error or prints no usable output, including after that retry, tell the user it failed, include the error text, and stop. Don't continue with partial instructions.
|
|
22
22
|
|
|
23
|
+
If `automatica11y` is already installed (`automatica11y --version` prints a version), you can run `automatica11y guide skill` instead. The steps check that the installed version matches them.
|
|
24
|
+
|
|
23
25
|
After reading the steps, pass only settings they list as supported. If the user named a setting they don't list as supported, tell the user which setting is unsupported, list the supported values from the steps, and ask which to use. Don't ask again for anything they've already said.
|
|
24
26
|
|
|
25
27
|
4. If you can't run shell commands, or `npx` can't reach the npm registry, tell the user and stop. Don't guess at results.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: automatica11y-runner
|
|
3
|
-
compatibility: Needs Node 20 or newer, Chrome or Chromium, a shell that can run npx, and network access to the npm registry.
|
|
3
|
+
compatibility: Needs Node 20 or newer, Chrome or Chromium, a shell that can run npx or the installed automatica11y command, and network access to the npm registry.
|
|
4
4
|
description: The full steps for running automatica11y audits and comparisons, from building the command to writing the report. Use it when the automatica11y skill or AGENTS.md sends you here, or when you've already been told to run automatica11y and need the steps.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -16,13 +16,15 @@ The tool is **not** an attestation or certification tool. Automated checks cover
|
|
|
16
16
|
|
|
17
17
|
Do sections 1 and 2 before you run an audit. A question to the user about a missing target or an unsupported setting (section 3) can come before or after them.
|
|
18
18
|
|
|
19
|
-
This skill works with automatica11y **0.
|
|
19
|
+
This skill works with automatica11y **0.6.x**. Run:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
npx --yes automatica11y@latest --version
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
If the command exits with an error or prints nothing, tell the user it failed, include the error text, and stop. If the output doesn't start with `0.
|
|
25
|
+
If the command exits with an error or prints nothing, tell the user it failed, include the error text, and stop. If the output doesn't start with `0.6.`, stop. Tell the user the version you got and the series this copy expects (`0.6.x`). If this copy came from a file, offer to read the matching steps with `npx --yes automatica11y@latest guide skill`. Don't run an audit until the user confirms how to proceed.
|
|
26
|
+
|
|
27
|
+
If the user installed the tool globally (`npm i -g automatica11y`), `automatica11y` is on the `PATH` (so is the short name `a11y`), and it runs the same commands as `npx --yes automatica11y@latest`. Every command in this skill is written with `npx`, so replace that prefix with `automatica11y` only when `automatica11y --version` passes the check above. If the installed version is the wrong series, or nothing is installed, use `npx`.
|
|
26
28
|
|
|
27
29
|
## 2. Check the setup
|
|
28
30
|
|
|
@@ -52,6 +54,19 @@ Use `audit` for one target and `compare` for two or more, even when they're diff
|
|
|
52
54
|
|
|
53
55
|
A bare word such as `button` is a path: the folder or file `./button`. Always write an npm package with the `npm:` prefix. If a word could mean a package or a folder, ask the user which one before you run anything.
|
|
54
56
|
|
|
57
|
+
**Plain HTML libraries.** A library with no framework, such as a stylesheet package and a script package, is tested by naming the files the page should load as sub-paths: `npm:pkg/base.css,pkg/components.css,other/behavior.iife.js`. A sub-path ending in `.css` is a stylesheet, and one ending in `.js` or `.mjs` is a script. Read the package's documentation or its `exports` to find the right files, and ask the user which stylesheets and scripts they use when the documentation doesn't say. Nothing is generated for plain HTML. Button, link, form-field, dialog, accordion, live-region, menu, and tooltip have templates of bare native markup, so they run with no fixture, and the report says what that does and doesn't show. Say so plainly: a pass on a native template never means the package's own components pass. Tabs, combobox, and chart need a fixture (`fixtures/<id>/<archetype>.html`) or they're gaps. To test the package's own components, write a fixture from its documentation.
|
|
58
|
+
|
|
59
|
+
**One target can be several packages.** Some libraries ship in pieces, such as a stylesheet package and a script package. Join them with commas and no spaces: `npm:@scope/components,@scope/interactions`. They install into one folder and are tested as one target. The first is the primary: it names the target and decides the framework. Every other package is a companion. Each entry can have its own version and sub-path (`npm:a@1.2,b/button`). A list with a space, an empty entry, or the same package twice is rejected. Targets themselves are separated by spaces, as before.
|
|
60
|
+
|
|
61
|
+
**Reading a comparison in plain language.** People write "compare A, B and C and D." Work out the targets before you build the command:
|
|
62
|
+
|
|
63
|
+
- A comma joins packages into one target. The words "and", "vs", "versus", "against", and "compared with" separate targets. So "compare A, B and C and D" is three targets: `A,B`, `C`, and `D`.
|
|
64
|
+
- Give each target a short label that says what it is, such as `pantoken-html=npm:A,B instui=npm:C pantoken-wc=npm:D`. Use the framework or the library's own name, so the report reads clearly.
|
|
65
|
+
- Packages from the same organization are separate targets when they're alternatives for the same job (a React build and a web component build of one design system). They belong in one target only when the user says they work together, or when one package is the stylesheet or script half of the other.
|
|
66
|
+
- Don't guess when the grouping is unclear. "A, B and C" could be one target of three packages, a target of two beside a third, or three targets. A comma list with no "and", a package that might be a companion or an alternative, and "with" or "plus" between packages are all unclear.
|
|
67
|
+
- Always show the grouping before you run, as a short table of label, packages, and role. If the grouping came from anything but an obvious reading, ask the user to confirm it, with your best reading offered as the default, in the same message as your other questions.
|
|
68
|
+
- Run the same archetypes on every target in a comparison. Say in the report which targets are made of several packages.
|
|
69
|
+
|
|
55
70
|
Options you can set, and nothing else:
|
|
56
71
|
|
|
57
72
|
| Option | Default | Use it when |
|
|
@@ -204,7 +219,7 @@ Use the structure of `report.md`. Quote selectors and rule IDs exactly as `resul
|
|
|
204
219
|
|
|
205
220
|
Say these things plainly. Don't soften them, and don't fill in a result.
|
|
206
221
|
|
|
207
|
-
- **Unsupported framework.** The package needs a framework other than React, Vue 3, or web components. Name it, and say which version is unsupported (Vue 2, for example). This version covers React, Vue 3, and web components. A Storybook for the library still works, whatever the framework.
|
|
222
|
+
- **Unsupported framework.** The package needs a framework other than React, Vue 3, Angular 22 and newer, or web components. Name it, and say which version is unsupported (Vue 2 or Angular 21, for example). This version covers React, Vue 3, Angular 22 and newer, and web components. A Storybook for the library still works, whatever the framework.
|
|
208
223
|
- **Not applicable.** The package has no rendering surface, such as a utility library. There's nothing to test.
|
|
209
224
|
- **Not testable.** The content is a canvas with no alternative, or sits in a closed shadow root. The rule engines can't see it, so the result is untested, not clean. The virtual screen reader also can't read open shadow roots.
|
|
210
225
|
- **Gap.** The archetype has no usable fixture or no matching export. Say what the archetype needs.
|
|
@@ -46,6 +46,28 @@ For React, JSX works without importing React. Import the library from its packag
|
|
|
46
46
|
|
|
47
47
|
For Vue 3, a fixture is also JSX, and the default export is a component: a function that returns JSX is a functional component, or you can export a component object. The tool turns JSX into `h()` calls and supplies `h` and `Fragment` itself, so don't import them. Importing `h` fails the bundle with "symbol already declared". JSX children become the default slot. Vue's reactivity works, so `import { ref } from "vue"` for state, and wrap the JSX in `defineComponent({ setup() { ... return () => (<jsx/>) } })`. A library that needs a plugin, a theme, or global components can export a function named `setup(app)`, which runs before the app mounts, for example `export function setup(app) { app.use(plugin) }`. Named slots and `v-model` aren't JSX syntax. Pass a slots object as the children, or set `modelValue` and `onUpdate:modelValue` with a spread, for example `{...{ "onUpdate:open": (next) => (open.value = next) }}`.
|
|
48
48
|
|
|
49
|
+
For Angular 22 and newer, a fixture is a plain `.js` file with no decorator syntax. Define each component by calling the decorator as a function, and make the component class the default export. The tool loads Angular's runtime compiler, so libraries that ship partly compiled work as they are. Import what you use in the component's `imports` list, and export `providers` (an array) when the library needs application providers. State lives in signals. For example:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { Component, signal } from "@angular/core";
|
|
53
|
+
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
|
|
54
|
+
|
|
55
|
+
class Fixture { shown = signal(false); }
|
|
56
|
+
Component({
|
|
57
|
+
selector: "app-fixture",
|
|
58
|
+
imports: [MatMenu, MatMenuItem, MatMenuTrigger],
|
|
59
|
+
template: `<button type="button" data-a11y-trigger [matMenuTriggerFor]="m">Actions</button>
|
|
60
|
+
<mat-menu #m="matMenu"><button mat-menu-item>Copy</button></mat-menu>`,
|
|
61
|
+
})(Fixture);
|
|
62
|
+
export default Fixture;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
An Angular fixture can also be TypeScript (`fixtures/<target id>/<archetype>.ts`) with `@Component({ ... })` decorators. The tool strips the types, but it can't emit constructor parameter metadata, so get services with `inject(Service)` and not through constructor parameters. A missing provider or an unknown element is reported in a plain sentence with Angular's error code.
|
|
66
|
+
|
|
67
|
+
Angular libraries split their parts across sub-paths, so audit them with a sub-path target, for example `npm:@angular/material/menu`.
|
|
68
|
+
|
|
69
|
+
For plain HTML, a fixture is a `.html` file of markup (`fixtures/<target id>/<archetype>.html`). It's a snippet, not a whole page: no `<html>` or `<body>`. Put `data-a11y-trigger` and `data-a11y-root` on the right elements yourself. The target says what loads, as sub-paths in a list: `npm:pkg/base.css,pkg/components.css,other/behavior.iife.js`. Stylesheets load with the page, the markup goes on next, and the scripts load last, so a script that wires up its elements when it starts finds them. A `<script>` inside the snippet runs. Use the classes and attributes the library's documentation names, and don't invent them.
|
|
70
|
+
|
|
49
71
|
For web components, make `mount(container)` the file's default export. It adds the archetype to the container. The tool imports the package first, so its elements are defined before `mount` runs. The `data-a11y-trigger` and `data-a11y-root` attributes can sit on a host element, a slotted child, or an element inside an **open** shadow root. A **closed** shadow root hides its content from every tool, so the report lists it as not testable.
|
|
50
72
|
|
|
51
73
|
## Library accessibility options
|
package/src/cli.js
CHANGED
package/src/commands/common.js
CHANGED
|
@@ -14,7 +14,7 @@ export const EXIT = { OK: 0, FAIL_THRESHOLD: 1, USAGE: 2, ENVIRONMENT: 3, ALL_TA
|
|
|
14
14
|
export class UsageError extends Error {}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @typedef {{ stdout: { write(text: string): unknown }, stderr: { write(text: string): unknown }, cwd: string, env: NodeJS.ProcessEnv, fetch?: import("../plan/classify.js").FetchLike, npmView?: (spec: string) => Promise<
|
|
17
|
+
* @typedef {{ stdout: { write(text: string): unknown }, stderr: { write(text: string): unknown }, cwd: string, env: NodeJS.ProcessEnv, fetch?: import("../plan/classify.js").FetchLike, npmView?: (spec: string) => Promise<unknown>, installPackage?: Function, platform?: NodeJS.Platform }} Io
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const OPTION_DEFS = /** @type {const} */ ({
|
|
@@ -62,7 +62,8 @@ export function parseRunArgs(command, argv) {
|
|
|
62
62
|
} catch (error) {
|
|
63
63
|
throw new UsageError(error instanceof Error ? error.message.split(". ")[0] : String(error));
|
|
64
64
|
}
|
|
65
|
-
const {
|
|
65
|
+
const values = /** @type {{ [K in keyof typeof OPTION_DEFS]?: typeof OPTION_DEFS[K]["type"] extends "boolean" ? boolean : string }} */ (parsed.values);
|
|
66
|
+
const { positionals } = parsed;
|
|
66
67
|
if (values.help) return { help: true, targets: [], planOnly: false, options: null };
|
|
67
68
|
|
|
68
69
|
if (command === "audit" && positionals.length !== 1) {
|
|
@@ -99,16 +100,16 @@ export function parseRunArgs(command, argv) {
|
|
|
99
100
|
const ibm = ibmFlag === undefined ? null : Number(parseChoice("fail-on-ibm", ibmFlag, TOOLKIT_LEVELS.map(String)));
|
|
100
101
|
if (axe && !engines.includes("axe")) throw new UsageError("--fail-on-axe needs the axe engine. Add axe to --engine or drop the flag.");
|
|
101
102
|
if (ibm && !engines.includes("ibm")) throw new UsageError("--fail-on-ibm needs the ibm engine. Add ibm to --engine or drop the flag.");
|
|
102
|
-
fail = { mode: /** @type {"any" | "all"} */ (modeFlag === undefined ? "any" : parseChoice("fail-mode", modeFlag, FAIL_MODES)), axe
|
|
103
|
+
fail = { mode: /** @type {"any" | "all"} */ (modeFlag === undefined ? "any" : parseChoice("fail-mode", modeFlag, FAIL_MODES)), axe, ibm };
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const options = {
|
|
106
|
-
wcag:
|
|
107
|
-
level:
|
|
108
|
-
tiers
|
|
109
|
-
engines
|
|
110
|
-
libA11y
|
|
111
|
-
archetypes
|
|
107
|
+
wcag: values.wcag === undefined ? "2.2" : parseChoice("wcag", values.wcag, WCAG_VERSIONS),
|
|
108
|
+
level: values.level === undefined ? "AA" : parseChoice("level", values.level, LEVELS),
|
|
109
|
+
tiers,
|
|
110
|
+
engines,
|
|
111
|
+
libA11y,
|
|
112
|
+
archetypes,
|
|
112
113
|
mapping: values.mapping ?? null,
|
|
113
114
|
maxStories,
|
|
114
115
|
generate: !values["no-generate"],
|
|
@@ -122,7 +123,7 @@ export function parseRunArgs(command, argv) {
|
|
|
122
123
|
export function describeTarget(target) {
|
|
123
124
|
if (target.status === "failed") return `failed: ${target.reason}`;
|
|
124
125
|
const r = target.resolved ?? {};
|
|
125
|
-
if (target.kind?.startsWith("npm")) return `${r.name}@${r.version ?? r.requested ?? "latest"}${r.subpath ? `/${r.subpath}` : ""}${r.framework ? ` (${r.framework})` : ""}`;
|
|
126
|
+
if (target.kind?.startsWith("npm")) return `${r.name}@${r.version ?? r.requested ?? "latest"}${r.subpath ? `/${r.subpath}` : ""}${target.companions?.length ? ` with ${target.companions.map((c) => `${c.name}@${c.version ?? c.requested ?? "latest"}${c.subpath ? `/${c.subpath}` : ""}`).join(", ")}` : ""}${r.framework ? ` (${r.framework})` : ""}`;
|
|
126
127
|
return r.url ?? r.path ?? "";
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -256,7 +257,8 @@ const FLAGS = `Options:
|
|
|
256
257
|
/** @param {string} [command] */
|
|
257
258
|
export function usage(command) {
|
|
258
259
|
const header = {
|
|
259
|
-
audit: "Usage: automatica11y audit <target> [options]\n\nCheck how accessible one target is.\n\n",
|
|
260
|
+
audit: "Usage: automatica11y audit <target> [options]\n automatica11y check <target> [options]\n\nCheck how accessible one target is.\n\n",
|
|
261
|
+
check: "Usage: automatica11y audit <target> [options]\n automatica11y check <target> [options]\n\nCheck how accessible one target is.\n\n",
|
|
260
262
|
compare: "Usage: automatica11y compare <target> <target> [<target>...] [options]\n\nCompare two or more targets.\n\n",
|
|
261
263
|
run: "Usage: automatica11y run --plan <plan.json>\n\nRe-run a saved plan.\n",
|
|
262
264
|
}[command ?? ""];
|
|
@@ -266,6 +268,7 @@ export function usage(command) {
|
|
|
266
268
|
}
|
|
267
269
|
return `Usage:
|
|
268
270
|
automatica11y audit <target> [options]
|
|
271
|
+
automatica11y check <target> [options]
|
|
269
272
|
automatica11y compare <target> <target> [<target>...] [options]
|
|
270
273
|
automatica11y run --plan <plan.json>
|
|
271
274
|
automatica11y doctor
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular's own errors are written for someone with a stack trace and the source open. A report is read without either, so the
|
|
3
|
+
* ones a fixture is likely to hit get a plain sentence that keeps Angular's code and the name it reports. Anything else passes through.
|
|
4
|
+
* @param {string} text The first line of an error.
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function explainAngularError(text) {
|
|
8
|
+
const code = /\bNG0*(\d+)\b/.exec(text)?.[1];
|
|
9
|
+
if (!code) return text;
|
|
10
|
+
const quoted = /`([^`]+)`|'([^']+)'|"([^"]+)"/.exec(text);
|
|
11
|
+
const name = quoted?.[1] ?? quoted?.[2] ?? quoted?.[3];
|
|
12
|
+
switch (code) {
|
|
13
|
+
case "201":
|
|
14
|
+
case "200":
|
|
15
|
+
return `the fixture is missing a provider that the library needs${name ? ` (${name})` : ""}. Add it to the fixture's \`providers\` export, or put the part that provides it around this one (NG0${code})`;
|
|
16
|
+
case "303":
|
|
17
|
+
case "304":
|
|
18
|
+
case "8002":
|
|
19
|
+
return `the template uses an element or input that the library doesn't define${name ? ` (${name})` : ""}, or the component isn't in the template's imports (NG0${code})`;
|
|
20
|
+
case "5105":
|
|
21
|
+
return "the library animates with a legacy animations package that isn't installed in the fixture (NG05105)";
|
|
22
|
+
case "908":
|
|
23
|
+
return "Angular couldn't find the root element to start from (NG0908)";
|
|
24
|
+
default:
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular describes what turns a component or directive on with selectors. At runtime a class carries them in Ivy's own form:
|
|
3
|
+
* each alternative is an array that starts with the element name (empty for an attribute-only selector), followed by
|
|
4
|
+
* attribute names and values: `button[matButton]` is `["button", "matButton", ""]`. Numbers in the array mark class names,
|
|
5
|
+
* `:not()` parts, and other forms that a template can't write from a name alone, so those alternatives are skipped.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Is this alternative plain: an element name and attributes, nothing else? */
|
|
9
|
+
const isPlain = (alternative) => Array.isArray(alternative) && alternative.length % 2 === 1 && alternative.every((part) => typeof part === "string");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The simplest element and attributes that match one of the selectors, preferring a given element.
|
|
13
|
+
* An attribute-only selector (`[uiTooltip]`) gets the preferred element, or `fallback` when none is preferred.
|
|
14
|
+
* @param {unknown[][]} selectors
|
|
15
|
+
* @param {{ prefer?: string, fallback?: string }} [options]
|
|
16
|
+
* @returns {{ tag: string, attrs: Array<[string, string]> } | null}
|
|
17
|
+
*/
|
|
18
|
+
export function markupFor(selectors, { prefer, fallback } = {}) {
|
|
19
|
+
const plain = (selectors ?? []).filter(isPlain).map((alt) => ({ tag: alt[0], attrs: pairs(alt) }));
|
|
20
|
+
const choose = (list) => list.sort((a, b) => a.attrs.length - b.attrs.length)[0] ?? null;
|
|
21
|
+
const match = prefer ? choose(plain.filter((alt) => alt.tag === prefer)) ?? choose(plain.filter((alt) => alt.tag === "")) : choose(plain.filter((alt) => alt.tag !== "")) ?? choose(plain);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
const tag = match.tag || prefer || fallback;
|
|
24
|
+
return tag ? { tag, attrs: match.attrs } : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pairs(alternative) {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 1; i < alternative.length; i += 2) out.push([alternative[i], alternative[i + 1]]);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The attributes as they're written in a template: `matButton`, or `kind="primary"`. */
|
|
34
|
+
export function attributeText(attrs) {
|
|
35
|
+
return attrs.map(([name, value]) => (value ? `${name}="${String(value).replace(/"/g, """)}"` : name)).join(" ");
|
|
36
|
+
}
|