@surea11y/core 1.0.0 → 1.0.1
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/CHANGELOG.md +17 -0
- package/README.md +382 -81
- package/docs/ENGINE_OPTIONS.md +66 -1
- package/docs/I18N.md +3 -3
- package/docs/OUTPUT_SCHEMA.md +5 -5
- package/docs/RULE_TAXONOMY.md +9 -8
- package/docs/TROUBLESHOOTING.md +2 -2
- package/docs/WCAG_CONFORMANCE.md +1 -1
- package/package.json +2 -2
- package/src/checks/automatic/aria-allowed-attr.js +47 -24
- package/src/core.js +978 -39
- package/src/i18n/fr.js +314 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,9 +4,26 @@ All notable changes to this project are documented here, in [Keep a Changelog](h
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
### Added
|
|
8
|
+
- Completed French (`fr`) localization: translated the 313 remaining `src/i18n/fr.js` keys, bringing French to full parity with English (600/600 keys, up from 287/600). Verified against a live scan (`locale: 'fr'`) and confirmed no key/placeholder mismatches against `src/i18n/en.js`. Landmark terminology uses "point de repère" per MDN's French ARIA documentation.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `docs/RULE_TAXONOMY.md`: automatic rules' allowed outcomes was missing `cantTell` (4 rules use it as a defensive fallback); the "current intents"/"current families" lists were stale and read as exhaustive when the ruleset actually spans 54 suffixes/68 prefixes — reframed as illustrative with a pointer to the generated `RULE_CATALOG.md`; `data.visibilityFilter.targetSet` was missing the `'dom'` value (only `'acc'` was listed).
|
|
12
|
+
- `docs/OUTPUT_SCHEMA.md`: the `visibilityFilter` type was missing its always-present `eligible` field; the worked example's `structuralPath` values for the button/img pair were swapped; removed a citation to a note in `RULE_AUTHORING.md` that doesn't exist there.
|
|
13
|
+
- `docs/I18N.md`: stale key counts (`en` listed as 590, actual 600; `fr` coverage listed as ~49%, actual ~48% at the time) — now updated to reflect full parity.
|
|
14
|
+
|
|
15
|
+
## [1.0.1] - 2026-07-28
|
|
16
|
+
|
|
7
17
|
### Changed
|
|
8
18
|
- Trimmed the published npm package: rule-authoring scaffolding (`docs/RULE_TEMPLATE.*`, `docs/RULE_TEST_TEMPLATE.md`, `docs/RULE_TEST_AUTHORING.md`, `docs/TEST_OUTCOME_STABILITY.md`) and the not-yet-documented `src/explain` module no longer ship in the tarball — both stay in the git repo for contributors.
|
|
9
19
|
- README rewritten for clarity: corrected the install command and `require()` examples to the actual package name (`@surea11y/core`), and updated the rule count to 125.
|
|
20
|
+
- `docs/ENGINE_OPTIONS.md`: documented the previously-undocumented `visibilityMode` option (`'styleOnly'`/`'styleAndGeometry'`, scoped to the three contrast rules), and added a "Recipes" section with composed, runnable examples for common scenarios (CI gating, auditor-mode contrast passes, scoped re-scans, reproducible snapshots, custom rules).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- `aria-allowed-attr`'s `SUPPORTED_ATTRS_BY_ROLE` table reconciled against the published WAI-ARIA 1.2 Recommendation (via `aria-query`, not axe-core's table — axe-core's own source comments confirm many of its `aria-expanded` allowances are deliberate ARIA 1.1 legacy carryovers, not current-spec facts). Added `aria-expanded` to 10 roles (checkbox, columnheader, gridcell, listbox, menuitemcheckbox, menuitemradio, row, rowheader, switch, tab) and `aria-activedescendant` to 8 composite-widget roles (combobox, grid, listbox, radiogroup, row, spinbutton, tablist, treegrid), plus smaller posinset/setsize/readonly/required/level gaps; removed `tree`'s unverified `aria-readonly`. `listitem` was already correct and is unchanged.
|
|
24
|
+
- README: a "Real browser execution" code sample passed four positional arguments to `page.evaluate()` and claimed it worked with "any" automation framework — Playwright's `page.evaluate()` only accepts one argument alongside the function and throws on this exact pattern. Now shown as Puppeteer-specific, with a pointer to `INTEGRATION.md`'s wrapper for Playwright.
|
|
25
|
+
- README: the JSON output example referenced a nonexistent rule id (`link-name-quality`); corrected to the real id, `link-name-quality-manual`.
|
|
26
|
+
- README: Quick Start code samples labeled the runner's four positional arguments as `url, ruleFilter, options, policy`; corrected to the actual names (`pageUrl, contextSelector, engineOptions, runOnly`) used consistently elsewhere in the docs.
|
|
10
27
|
|
|
11
28
|
## [1.1.1] - 2026-07-24
|
|
12
29
|
|
package/README.md
CHANGED
|
@@ -1,75 +1,216 @@
|
|
|
1
|
-
# surea11y
|
|
1
|
+
# @surea11y/core
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
> **Reliable accessibility testing for real web applications.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
surea11y is an accessibility engine designed to help development teams
|
|
6
|
+
identify objective accessibility issues early in the software lifecycle.
|
|
7
|
+
It runs against either static HTML or fully rendered browser pages,
|
|
8
|
+
producing deterministic, standards-traceable results that are suitable
|
|
9
|
+
for local development, automated testing and CI/CD pipelines.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **Runs anywhere your DOM does — jsdom or a real browser, each with real tradeoffs.** See "Static HTML vs. a live browser" below before picking one.
|
|
12
|
-
- **Fully localized output** (English, French, more on the way) without sacrificing stable, locale-independent rule IDs and machine-readable keys.
|
|
13
|
-
- **Extensible.** Register custom rules at scan time, filter by rule ID/tag/WCAG version, or scan several regions of a page in a single pass.
|
|
11
|
+
Unlike browser extensions or cloud-based services, surea11y is a
|
|
12
|
+
library-first project. You install it, run it where your code runs, and
|
|
13
|
+
receive structured results that can be consumed by people, scripts or
|
|
14
|
+
reporting tools.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Why surea11y?
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Accessibility automation is only valuable if developers can trust its
|
|
19
|
+
results.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
surea11y is built around a conservative philosophy: **never report
|
|
22
|
+
certainty when certainty cannot be established objectively.**
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
Instead of relying on heuristics that may generate false positives, each
|
|
25
|
+
rule makes a single deterministic decision. If a violation can be
|
|
26
|
+
proven, the outcome is `fail`. If human judgement is required, the
|
|
27
|
+
engine reports `cantTell` instead of guessing.
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
This makes the engine predictable, reproducible and suitable for
|
|
30
|
+
automated quality gates.
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
### Key principles
|
|
33
|
+
|
|
34
|
+
- **Deterministic execution.** The same input always produces the same
|
|
35
|
+
output.
|
|
36
|
+
- **Conservative by design.** `fail` is reserved for objective,
|
|
37
|
+
normative violations, minimizing false alarms.
|
|
38
|
+
- **Standards traceability.** Rules map to the applicable WCAG Success
|
|
39
|
+
Criterion whenever appropriate.
|
|
40
|
+
- **Stable machine-readable output.** Rule identifiers remain stable
|
|
41
|
+
regardless of language.
|
|
42
|
+
- **Framework independent.** Run inside jsdom or any browser
|
|
43
|
+
automation framework.
|
|
44
|
+
- **Extensible.** Add custom rules, register policies and filter scans
|
|
45
|
+
by rule IDs, tags or WCAG version.
|
|
46
|
+
- **Localized reporting.** Human-readable messages can be translated
|
|
47
|
+
without affecting machine-readable data.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Choosing the right execution model
|
|
52
|
+
|
|
53
|
+
surea11y supports two complementary execution models. Choosing the
|
|
54
|
+
correct one is essential because it determines which parts of the page
|
|
55
|
+
the engine can inspect.
|
|
56
|
+
|
|
57
|
+
### Static HTML
|
|
58
|
+
|
|
59
|
+
The CLI (`scan file.html` or `scan https://example.com`) and
|
|
60
|
+
`runDomRulesInPage()` analyse HTML without executing page JavaScript.
|
|
61
|
+
|
|
62
|
+
This approach is ideal for:
|
|
63
|
+
|
|
64
|
+
- static websites;
|
|
65
|
+
- server-side rendered applications;
|
|
66
|
+
- generated documentation;
|
|
67
|
+
- pre-rendered HTML.
|
|
68
|
+
|
|
69
|
+
Because JavaScript is never executed, the engine cannot inspect content
|
|
70
|
+
added after page load. Likewise, rules requiring real layout information
|
|
71
|
+
cannot be evaluated and will report `notApplicable` instead of making
|
|
72
|
+
assumptions.
|
|
73
|
+
|
|
74
|
+
### Fully rendered browser
|
|
75
|
+
|
|
76
|
+
`runa11yCoreInPage()` executes directly inside a live browser page.
|
|
77
|
+
|
|
78
|
+
This allows the engine to inspect:
|
|
79
|
+
|
|
80
|
+
- client-rendered applications;
|
|
81
|
+
- hydrated pages;
|
|
82
|
+
- computed styles;
|
|
83
|
+
- runtime DOM changes;
|
|
84
|
+
- layout-dependent rules.
|
|
85
|
+
|
|
86
|
+
The function is browser-agnostic and can be executed through Puppeteer,
|
|
87
|
+
Playwright, Selenium, Cypress or any environment capable of evaluating
|
|
88
|
+
JavaScript inside the page.
|
|
89
|
+
|
|
90
|
+
For a detailed comparison of both execution models, see
|
|
91
|
+
`docs/LIMITATIONS.md` and `docs/INTEGRATION.md`.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
Install the core package from npm:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
27
100
|
npm install @surea11y/core
|
|
28
101
|
```
|
|
29
102
|
|
|
30
|
-
|
|
103
|
+
The core engine has no runtime dependencies — requiring the library
|
|
104
|
+
itself never loads `jsdom`. Only the bundled CLI loads it, and only
|
|
105
|
+
when you actually run a scan, keeping the library lightweight and
|
|
106
|
+
suitable for embedding into your own tooling.
|
|
107
|
+
|
|
108
|
+
---
|
|
31
109
|
|
|
32
|
-
|
|
110
|
+
## Quick Start
|
|
33
111
|
|
|
34
|
-
|
|
112
|
+
### CLI
|
|
113
|
+
|
|
114
|
+
The CLI is the fastest way to analyse a page without writing any code.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
35
117
|
npx @surea11y/core scan ./index.html
|
|
36
118
|
npx @surea11y/core scan https://example.com/
|
|
37
119
|
```
|
|
38
120
|
|
|
39
|
-
|
|
121
|
+
The CLI analyses static HTML. It does not execute client-side
|
|
122
|
+
JavaScript, making it ideal for static sites and server-rendered
|
|
123
|
+
applications.
|
|
124
|
+
|
|
125
|
+
For available options, exit codes and advanced usage, see `docs/CLI.md`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### Library
|
|
40
130
|
|
|
41
|
-
|
|
131
|
+
surea11y exposes two entry points depending on where your code executes.
|
|
42
132
|
|
|
43
|
-
|
|
133
|
+
#### Node.js + jsdom
|
|
134
|
+
|
|
135
|
+
Use `runDomRulesInPage()` when your application already has a DOM
|
|
136
|
+
available through jsdom.
|
|
44
137
|
|
|
45
138
|
```js
|
|
46
|
-
|
|
47
|
-
const {
|
|
48
|
-
|
|
139
|
+
const { JSDOM } = require("jsdom");
|
|
140
|
+
const { runDomRulesInPage } = require("@surea11y/core");
|
|
141
|
+
|
|
142
|
+
const dom = new JSDOM('<img src="logo.png">', {
|
|
143
|
+
url: "https://example.com/",
|
|
144
|
+
pretendToBeVisual: true
|
|
145
|
+
});
|
|
49
146
|
|
|
50
|
-
const dom = new JSDOM('<img src="logo.png">', { url: 'https://example.com/', pretendToBeVisual: true });
|
|
51
147
|
global.window = dom.window;
|
|
52
148
|
global.document = dom.window.document;
|
|
53
149
|
|
|
54
|
-
const result = runDomRulesInPage(
|
|
55
|
-
|
|
56
|
-
//
|
|
150
|
+
const result = runDomRulesInPage(
|
|
151
|
+
"https://example.com/", // pageUrl
|
|
152
|
+
null, // contextSelector
|
|
153
|
+
{}, // engineOptions
|
|
154
|
+
null // runOnly
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
console.log(
|
|
158
|
+
result.checksResults.filter(r => r.outcome === "fail")
|
|
159
|
+
);
|
|
57
160
|
```
|
|
58
161
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
162
|
+
This approach is fast, simple and requires no browser installation. It
|
|
163
|
+
is best suited for static HTML and server-rendered pages.
|
|
164
|
+
|
|
165
|
+
---
|
|
62
166
|
|
|
63
|
-
|
|
167
|
+
#### Real browser execution
|
|
168
|
+
|
|
169
|
+
For client-rendered applications, execute the engine inside the page
|
|
170
|
+
itself.
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
// Puppeteer
|
|
174
|
+
const { runa11yCoreInPage } = require("@surea11y/core");
|
|
175
|
+
|
|
176
|
+
const result = await page.evaluate(
|
|
177
|
+
runa11yCoreInPage,
|
|
178
|
+
"https://example.com/", // pageUrl
|
|
179
|
+
null, // contextSelector
|
|
180
|
+
{}, // engineOptions
|
|
181
|
+
null // runOnly
|
|
182
|
+
);
|
|
64
183
|
```
|
|
65
184
|
|
|
66
|
-
`runa11yCoreInPage` is
|
|
185
|
+
`runa11yCoreInPage` is self-contained (its whole rule catalog is
|
|
186
|
+
inlined), so it works unmodified with any framework capable of
|
|
187
|
+
evaluating a function inside the page — Puppeteer, Selenium, Cypress,
|
|
188
|
+
browser extensions and custom drivers can all call it exactly as
|
|
189
|
+
shown above. **Playwright is the one exception**: its `page.evaluate()`
|
|
190
|
+
only accepts a single argument alongside the function, so
|
|
191
|
+
`runa11yCoreInPage` needs a one-argument wrapper instead of four
|
|
192
|
+
positional arguments — see `docs/INTEGRATION.md` for the exact pattern.
|
|
193
|
+
|
|
194
|
+
This allows the engine to inspect the fully rendered DOM exactly as
|
|
195
|
+
users experience it.
|
|
196
|
+
|
|
197
|
+
For complete integration examples, see `docs/INTEGRATION.md`.
|
|
67
198
|
|
|
68
|
-
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Understanding the Results
|
|
202
|
+
|
|
203
|
+
Every scan returns a structured JSON document designed for both
|
|
204
|
+
developers and automated tooling.
|
|
205
|
+
|
|
206
|
+
A simplified example looks like this:
|
|
69
207
|
|
|
70
208
|
```json
|
|
71
209
|
{
|
|
72
|
-
"engine": {
|
|
210
|
+
"engine": {
|
|
211
|
+
"tag": "a11ycore",
|
|
212
|
+
"schemaVersion": "1.0.0"
|
|
213
|
+
},
|
|
73
214
|
"url": "https://example.com/",
|
|
74
215
|
"checksResults": [
|
|
75
216
|
{
|
|
@@ -78,68 +219,228 @@ const result = await page.evaluate(runa11yCoreInPage, 'https://example.com/', nu
|
|
|
78
219
|
"severity": "serious",
|
|
79
220
|
"confidence": "high",
|
|
80
221
|
"occurrences": [
|
|
81
|
-
{
|
|
222
|
+
{
|
|
223
|
+
"selector": "html > body > img",
|
|
224
|
+
"summary": "Missing alt attribute on <img>.",
|
|
225
|
+
"hint": "Add an alt attribute or use alt=\"\" for decorative images."
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"ruleId": "link-name-quality-manual",
|
|
231
|
+
"outcome": "cantTell",
|
|
232
|
+
"severity": "minor",
|
|
233
|
+
"confidence": "medium",
|
|
234
|
+
"occurrences": [
|
|
235
|
+
{
|
|
236
|
+
"selector": "html > body > a:nth-child(3)",
|
|
237
|
+
"summary": "Link text may not be descriptive enough out of context.",
|
|
238
|
+
"hint": "Confirm the link text clearly describes its destination or purpose."
|
|
239
|
+
}
|
|
82
240
|
]
|
|
83
241
|
}
|
|
84
|
-
]
|
|
85
|
-
"rulesResults": []
|
|
242
|
+
]
|
|
86
243
|
}
|
|
87
244
|
```
|
|
88
245
|
|
|
89
|
-
|
|
246
|
+
The second result illustrates the engine's conservative stance: it can
|
|
247
|
+
confirm a link has text, but whether that text is genuinely descriptive
|
|
248
|
+
requires human judgement, so it reports `cantTell` instead of guessing.
|
|
249
|
+
|
|
250
|
+
Each finding contains enough information to answer four questions:
|
|
251
|
+
|
|
252
|
+
- **What failed?** (`ruleId`)
|
|
253
|
+
- **Why did it fail?** (`summary`)
|
|
254
|
+
- **Where did it fail?** (`selector` and occurrences)
|
|
255
|
+
- **How can it be fixed?** (`hint`)
|
|
256
|
+
|
|
257
|
+
The complete schema also includes confidence, severity, WCAG
|
|
258
|
+
traceability, composite rule results and other metadata intended for
|
|
259
|
+
reporting and automation.
|
|
260
|
+
|
|
261
|
+
For a complete field-by-field reference, see `docs/OUTPUT_SCHEMA.md`.
|
|
262
|
+
|
|
263
|
+
---
|
|
90
264
|
|
|
91
265
|
## Documentation
|
|
92
266
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
| [`docs/OUTPUT_SCHEMA.md`](./docs/OUTPUT_SCHEMA.md) | The full result shape — every field, every outcome value, worked examples. Start here. |
|
|
96
|
-
| [`docs/CLI.md`](./docs/CLI.md) | The `surea11y scan` command — flags, exit codes, what it can and can't scan. |
|
|
97
|
-
| [`docs/ENGINE_OPTIONS.md`](./docs/ENGINE_OPTIONS.md) | Every `engineOptions`/`runOnly` field: selecting rules, locale, shadow DOM, contrast mode, policy, and the common `runOnly` gotcha. |
|
|
98
|
-
| [`docs/INTEGRATION.md`](./docs/INTEGRATION.md) | Node/jsdom vs. real-browser (Puppeteer, Playwright, Selenium, Cypress, or any driver) usage, CI gating, browser-extension context. |
|
|
99
|
-
| [`docs/BINDING_AUTHORS_GUIDE.md`](./docs/BINDING_AUTHORS_GUIDE.md) | Building a *new* framework binding (Puppeteer, Cypress, ...)? What the engine gives you for free vs. what every binding has to build itself, checked against what `surea11y-playwright` actually needed. |
|
|
100
|
-
| [`docs/RULE_CATALOG.md`](./docs/RULE_CATALOG.md) | All 125 rules — id, WCAG SC, level, confidence, severity. Generated; run `npm run docs:rule-catalog` to refresh. |
|
|
101
|
-
| [`docs/WCAG_CONFORMANCE.md`](./docs/WCAG_CONFORMANCE.md) | How rule outcomes roll up to an SC-level / A-AA-AAA conformance picture, and what that picture does and doesn't claim. |
|
|
102
|
-
| [`docs/POLICY.md`](./docs/POLICY.md) | The `a11y`/`generic` policy contracts — what they control and how to customize. |
|
|
103
|
-
| [`docs/I18N.md`](./docs/I18N.md) | Current locale coverage and how to contribute a translation. |
|
|
104
|
-
| [`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md) | What this engine cannot do, and why — stated upfront. |
|
|
105
|
-
| [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md) | Common gotchas, FAQ. |
|
|
106
|
-
| [`docs/RULE_AUTHORING.md`](./docs/RULE_AUTHORING.md) | How to write a new rule — the exact module contract, a critical footgun to avoid, fixture requirements. |
|
|
107
|
-
| [`docs/RULE_TAXONOMY.md`](./docs/RULE_TAXONOMY.md) | How rules are categorized. |
|
|
108
|
-
| [`CONTRIBUTING.md`](./CONTRIBUTING.md) | How to add/change a rule, commit conventions, required checks before a PR. |
|
|
109
|
-
| [`SECURITY.md`](./SECURITY.md) | Scope, threat model, how to report a vulnerability. |
|
|
110
|
-
| [`CHANGELOG.md`](./CHANGELOG.md) | What changed, release to release. |
|
|
111
|
-
|
|
112
|
-
## Folder layout
|
|
267
|
+
The project documentation is organized by topic so you can start quickly
|
|
268
|
+
and progressively explore more advanced features.
|
|
113
269
|
|
|
114
|
-
|
|
270
|
+
| Document | Description |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `docs/OUTPUT_SCHEMA.md` | Complete description of every field returned by the engine. |
|
|
273
|
+
| `docs/CLI.md` | CLI commands, options, exit codes and examples. |
|
|
274
|
+
| `docs/ENGINE_OPTIONS.md` | Configuration, filtering, policies and localization. |
|
|
275
|
+
| `docs/INTEGRATION.md` | Using surea11y with jsdom, Playwright, Puppeteer, Selenium, Cypress and other drivers. |
|
|
276
|
+
| `docs/BINDING_AUTHORS_GUIDE.md` | Building new framework integrations on top of the engine. |
|
|
277
|
+
| `docs/RULE_CATALOG.md` | Reference of every built-in accessibility rule. |
|
|
278
|
+
| `docs/WCAG_CONFORMANCE.md` | Understanding WCAG rollups and conformance reporting. |
|
|
279
|
+
| `docs/POLICY.md` | Built-in policy contracts and customization. |
|
|
280
|
+
| `docs/I18N.md` | Translation support and localization. |
|
|
281
|
+
| `docs/LIMITATIONS.md` | Structural limitations of automated accessibility testing. |
|
|
282
|
+
| `docs/TROUBLESHOOTING.md` | Frequently asked questions and common issues. |
|
|
283
|
+
| `docs/RULE_AUTHORING.md` | Writing custom accessibility rules. |
|
|
284
|
+
| `docs/RULE_TAXONOMY.md` | Rule categorization model. |
|
|
285
|
+
| `CONTRIBUTING.md` | Contributing guidelines. |
|
|
286
|
+
| `SECURITY.md` | Security policy and vulnerability reporting. |
|
|
287
|
+
| `CHANGELOG.md` | Release history. |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Philosophy
|
|
292
|
+
|
|
293
|
+
surea11y is built on a simple principle:
|
|
294
|
+
|
|
295
|
+
> Automate what can be determined objectively. Never pretend to automate
|
|
296
|
+
> what cannot.
|
|
297
|
+
|
|
298
|
+
Accessibility is not something that can be reduced to a single score or
|
|
299
|
+
a binary pass/fail result. Some WCAG requirements can be evaluated with
|
|
300
|
+
complete confidence, while others require human judgement, knowledge of
|
|
301
|
+
context or usability evaluation.
|
|
302
|
+
|
|
303
|
+
Rather than hiding that distinction, surea11y makes it explicit.
|
|
304
|
+
|
|
305
|
+
That philosophy influences every rule in the engine and is the reason
|
|
306
|
+
outcomes such as `cantTell` and `notApplicable` exist. They communicate
|
|
307
|
+
uncertainty honestly instead of encouraging misleading conclusions.
|
|
308
|
+
|
|
309
|
+
### What surea11y won't catch
|
|
310
|
+
|
|
311
|
+
Being explicit about the boundaries of automation is part of the same
|
|
312
|
+
philosophy. For example, surea11y will not:
|
|
313
|
+
|
|
314
|
+
- confirm that alt text is *meaningful*, only that it is present
|
|
315
|
+
(an alt attribute of `"image123.png"` passes the objective check);
|
|
316
|
+
- judge whether a color contrast choice is aesthetically appropriate,
|
|
317
|
+
only whether it meets the applicable contrast ratio;
|
|
318
|
+
- determine whether an error message actually *explains* the problem,
|
|
319
|
+
since that depends on validation logic a static scan can't see;
|
|
320
|
+
- detect a keyboard focus trap or content clipped at 400% zoom, since
|
|
321
|
+
both require simulating real user interaction over time, not just
|
|
322
|
+
reading the DOM at one instant.
|
|
323
|
+
|
|
324
|
+
These are the cases where the engine reports `cantTell`, and where a
|
|
325
|
+
human reviewer's judgement remains necessary. See
|
|
326
|
+
`docs/LIMITATIONS.md` for the complete list of structural limitations.
|
|
327
|
+
|
|
328
|
+
The objective of the project is not to replace accessibility experts. It
|
|
329
|
+
is to remove repetitive verification work, provide reliable automated
|
|
330
|
+
feedback to developers and help teams integrate accessibility into their
|
|
331
|
+
normal development process.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Project Structure
|
|
336
|
+
|
|
337
|
+
The repository is organised so that the accessibility engine, rule
|
|
338
|
+
implementations and supporting infrastructure remain clearly separated.
|
|
339
|
+
|
|
340
|
+
```text
|
|
115
341
|
bin/
|
|
116
|
-
core.js
|
|
342
|
+
core.js # CLI entry point
|
|
343
|
+
|
|
117
344
|
src/
|
|
118
|
-
index.js
|
|
119
|
-
core.js
|
|
345
|
+
index.js # Public API
|
|
346
|
+
core.js # Generated runtime bundle
|
|
347
|
+
|
|
120
348
|
checks/
|
|
121
|
-
automatic/
|
|
122
|
-
manual/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
349
|
+
automatic/ # Deterministic automated rules
|
|
350
|
+
manual/ # Advisory rules (maximum outcome: cantTell)
|
|
351
|
+
|
|
352
|
+
core/ # Shared engine runtime
|
|
353
|
+
policy/ # Policy implementations
|
|
354
|
+
i18n/ # Localized messages
|
|
355
|
+
coverage/ # WCAG coverage definitions
|
|
356
|
+
catalogs/ # Composite rule catalogs
|
|
357
|
+
|
|
128
358
|
scripts/
|
|
129
|
-
build-core.js
|
|
130
|
-
|
|
359
|
+
build-core.js # Generates the runtime bundle
|
|
360
|
+
|
|
361
|
+
docs/ # Project documentation
|
|
362
|
+
|
|
131
363
|
tests/
|
|
132
|
-
fixtures/
|
|
133
|
-
engine-checks/
|
|
364
|
+
fixtures/ # Rule fixtures
|
|
365
|
+
engine-checks/ # Engine and rule tests
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
This separation allows the engine to evolve independently from framework
|
|
369
|
+
integrations while keeping the rule authoring experience consistent.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Building the Project
|
|
374
|
+
|
|
375
|
+
After cloning the repository, regenerate the bundled runtime:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
npm run build
|
|
134
379
|
```
|
|
135
380
|
|
|
136
|
-
|
|
381
|
+
To execute the complete test suite:
|
|
137
382
|
|
|
138
|
-
```
|
|
139
|
-
npm
|
|
140
|
-
npm test # build + run the full test suite
|
|
383
|
+
```bash
|
|
384
|
+
npm test
|
|
141
385
|
```
|
|
142
386
|
|
|
387
|
+
The test suite validates rule behaviour, fixture coverage and overall
|
|
388
|
+
engine consistency to ensure deterministic results across releases.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Contributing
|
|
393
|
+
|
|
394
|
+
Contributions are welcome.
|
|
395
|
+
|
|
396
|
+
Whether you are fixing a bug, improving documentation or implementing a
|
|
397
|
+
new accessibility rule, please keep the project's core principles in
|
|
398
|
+
mind:
|
|
399
|
+
|
|
400
|
+
- deterministic behaviour;
|
|
401
|
+
- objective rule evaluation;
|
|
402
|
+
- conservative outcome reporting;
|
|
403
|
+
- stable public APIs;
|
|
404
|
+
- standards traceability;
|
|
405
|
+
- comprehensive test coverage.
|
|
406
|
+
|
|
407
|
+
Before opening a pull request, please review `CONTRIBUTING.md` for the
|
|
408
|
+
complete development workflow and coding conventions.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Security
|
|
413
|
+
|
|
414
|
+
Security issues should be reported responsibly.
|
|
415
|
+
|
|
416
|
+
Please refer to `SECURITY.md` for the project's security policy,
|
|
417
|
+
supported versions and the preferred disclosure process.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
143
421
|
## License
|
|
144
422
|
|
|
145
|
-
|
|
423
|
+
This project is released under the MIT License.
|
|
424
|
+
|
|
425
|
+
See the accompanying `LICENSE` file for the complete license text.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Final Notes
|
|
430
|
+
|
|
431
|
+
surea11y was created with a simple goal: make accessibility testing
|
|
432
|
+
trustworthy enough to become part of everyday software engineering.
|
|
433
|
+
|
|
434
|
+
It does not attempt to replace manual accessibility reviews, usability
|
|
435
|
+
testing or expert judgement. Instead, it focuses on providing reliable
|
|
436
|
+
automated verification for the parts of accessibility that can be
|
|
437
|
+
evaluated objectively.
|
|
438
|
+
|
|
439
|
+
By combining deterministic rules, standards traceability, stable
|
|
440
|
+
machine-readable output and honest reporting of uncertainty, surea11y
|
|
441
|
+
enables teams to detect accessibility issues earlier, reduce regressions
|
|
442
|
+
and build more accessible products with confidence.
|
|
443
|
+
|
|
444
|
+
Accessibility is not a checkbox performed before release. It is an
|
|
445
|
+
engineering practice that benefits from continuous feedback, and
|
|
446
|
+
surea11y is designed to become one of those feedback loops.
|
package/docs/ENGINE_OPTIONS.md
CHANGED
|
@@ -82,6 +82,7 @@ const engineOptions = {
|
|
|
82
82
|
mode: 'strictConformance', // 'strictConformance' (default) | 'auditorAssist'
|
|
83
83
|
rootCanvasFallback: '#ffffff' // background assumed when the true root background isn't computable
|
|
84
84
|
},
|
|
85
|
+
visibilityMode: 'styleOnly', // 'styleOnly' (default) | 'styleAndGeometry' — see below; scoped to the contrast rules only
|
|
85
86
|
|
|
86
87
|
policyContract: 'a11y', // 'a11y' (default) | 'generic' | inline contract object — see POLICY.md
|
|
87
88
|
policy: { // optional overrides on top of policyContract
|
|
@@ -116,13 +117,77 @@ const engineOptions = {
|
|
|
116
117
|
| `timestamp` | Passed straight through to the result's top-level `timestamp` field; the engine does not generate one itself (deterministic-by-design). |
|
|
117
118
|
| `contrast.mode` | `strictConformance` (default): contrast rules stay silent (`notApplicable`/skip) whenever the true rendered background isn't confidently computable, to protect against false `fail`s. `auditorAssist`: trades some of that safety margin for more findings, intended for a human auditor who will double-check flagged cases, not for unattended CI gating. |
|
|
118
119
|
| `contrast.rootCanvasFallback` | The assumed page background color when it's not computable at all — only matters in `auditorAssist` mode. |
|
|
120
|
+
| `visibilityMode` | Controls how strict the three contrast rules (`contrast-minimum`, `contrast-enhanced`, `contrast-computable`) are about deciding a text node is actually eligible to check. **Not read by any other rule.** `'styleOnly'` (default): eligibility is CSS-only — `display`, `visibility`, `opacity`, ancestor-hiding, etc. `'styleAndGeometry'`: adds real layout checks (`getClientRects()`/`getBoundingClientRect()`) on top of that — text with no client rects, or zero width/height, is excluded too. Reach for `'styleAndGeometry'` when running under a real browser/Playwright-Puppeteer (`runa11yCoreInPage`) and you want contrast findings to reflect actual rendered layout rather than just computed style; under plain jsdom (`runDomRulesInPage`) there's no real layout engine, so `'styleAndGeometry'` mostly just adds `getBoundingClientRect()` zero-size checks, not true clipping/overflow detection — see [`LIMITATIONS.md`](./LIMITATIONS.md). |
|
|
119
121
|
| `policyContract` / `policy` | See [`POLICY.md`](./POLICY.md) — controls which outcomes/confidence values are allowed and whether manual rules' would-be `fail`s get coerced to `cantTell`. |
|
|
120
|
-
| `output.includeSelector` / `.includeHtml` | Only affects the small number of rules (currently 4 of
|
|
122
|
+
| `output.includeSelector` / `.includeHtml` | Only affects the small number of rules (currently 4 of 125) that rely on the engine's automatic selector/HTML fill-in rather than building their own — most rules set `selector`/`html` themselves inside `runInPage` and are unaffected by this option. Not a reliable way to strip selectors/HTML from all output. |
|
|
121
123
|
| `rules[ruleId]` | Passed through to that rule as `ctx.config`. The plumbing exists end-to-end, but **no shipped rule currently reads `ctx.config`** — this is infrastructure for future per-rule configurability, not a lever that changes any of today's 123 rules' behavior. |
|
|
122
124
|
| `probes` | An optional, JSON-safe evidence object your host application can supply (depth- and size-capped by the engine before rules see it, via `ctx.inputs.probes`) — for future rules that might accept externally-supplied signals (e.g. real layout measurements a static DOM scan can't compute itself). Not consumed by any current rule. |
|
|
123
125
|
| `perfStats` / `profileRules` | Debug-only. `perfStats: true` returns internal counters on the result's `perfStats` field; `profileRules: true` additionally adds a per-rule timing breakdown. Shape is not part of the stable output contract — don't build on it. |
|
|
124
126
|
| `pingWaitTime` / `frameWaitTime` | Only read by `runa11yCoreAcrossFrames` (see [`INTEGRATION.md`](./INTEGRATION.md#cross-frame-scanning-including-cross-origin)) — how long to wait for a child frame to answer a ping (default `500`ms) and a full run request (default `60000`ms) before treating it as unreachable. Ignored by `runDomRulesInPage`/`runa11yCoreInPage`. |
|
|
125
127
|
|
|
128
|
+
## Recipes — composing options for real scenarios
|
|
129
|
+
|
|
130
|
+
The reference above documents each option in isolation. These combine several at once, for scenarios you're likely to actually hit.
|
|
131
|
+
|
|
132
|
+
**CI gate: WCAG 2.2 AA only, ignore a third-party widget you don't control**
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
runDomRulesInPage(url, null, {
|
|
136
|
+
excludeSelectors: ['#cookie-banner', '.intercom-launcher'],
|
|
137
|
+
tags: { include: 'wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22a,wcag22aa' }
|
|
138
|
+
}, null);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Human auditor doing a deep contrast pass in a real browser** — trade some false-positive protection for more findings, and check real layout (not just computed style) since a real page is being driven. Shown with Puppeteer's `page.evaluate` (accepts multiple args); if you're on Playwright, wrap the four positional args into a single object first — see [`INTEGRATION.md`](./INTEGRATION.md):
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
const result = await page.evaluate(runa11yCoreInPage, url, null, {
|
|
145
|
+
contrast: { mode: 'auditorAssist' },
|
|
146
|
+
visibilityMode: 'styleAndGeometry'
|
|
147
|
+
}, null);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Scoped re-scan of one region after a UI change, skipping shadow DOM** — useful in a component-level test where you only care about the widget you just changed:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
runDomRulesInPage(url, '#checkout-form', {
|
|
154
|
+
includeShadowDom: false
|
|
155
|
+
}, { includeRuleIds: ['form-control-programmatic-label-present', 'button-name-present'] });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Reproducible output for snapshot testing** — pin a `timestamp` so two runs of the same HTML produce byte-identical JSON, and request the debug timing breakdown:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
runDomRulesInPage(url, null, {
|
|
162
|
+
timestamp: '2026-01-01T00:00:00Z',
|
|
163
|
+
perfStats: true,
|
|
164
|
+
profileRules: true
|
|
165
|
+
}, null);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**A custom, org-specific rule alongside the built-ins**, only for this one call:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
runDomRulesInPage(url, null, {
|
|
172
|
+
customRules: [{
|
|
173
|
+
id: 'org-no-inline-onclick',
|
|
174
|
+
meta: { title: 'No inline onclick handlers', defaultSeverity: 'moderate' },
|
|
175
|
+
runInPage(ctx) {
|
|
176
|
+
const els = ctx.helpers.queryAll('[onclick]');
|
|
177
|
+
const occurrences = els.map((el) => ({
|
|
178
|
+
selector: ctx.helpers.buildSelector(el),
|
|
179
|
+
html: el.outerHTML,
|
|
180
|
+
summary: 'Inline onclick handler found.',
|
|
181
|
+
hint: 'Move event handling into an external script.'
|
|
182
|
+
}));
|
|
183
|
+
return { ruleId: ctx.rule.ruleId, outcome: occurrences.length ? 'fail' : 'pass', severity: 'moderate', occurrences };
|
|
184
|
+
}
|
|
185
|
+
}]
|
|
186
|
+
}, null);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See the option-by-option table above for anything not shown here, and the `customRules` section immediately below for the full descriptor contract.
|
|
190
|
+
|
|
126
191
|
## `customRules` — runtime-registered rules (equivalent to the rule/check registration pattern used by other engines)
|
|
127
192
|
|
|
128
193
|
Every shipped rule is baked into `src/core.js` at build time. `engineOptions.customRules` is the runtime escape hatch: an array of rule descriptors registered for that one call only — nothing is added to the static catalog (`getRulesCatalog()`/`getChecksCatalog()`), and nothing persists between calls. This is deliberate, not a limitation to work around: surea11y already takes fresh `engineOptions` per call with no mutable global config (unlike some other engines, which need a `configure()`/`reset()` step against a shared runtime), and custom rules follow that same per-call model.
|