@surea11y/core 1.2.0 → 1.4.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/CHANGELOG.md +81 -7
- package/LICENSE +373 -21
- package/README.md +175 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +27 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +19 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +63 -18
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +63 -31
- package/src/checks/automatic/aria-allowed-attr.js +204 -80
- package/src/checks/automatic/aria-allowed-role.js +23 -7
- package/src/checks/automatic/aria-braille-equivalent.js +34 -10
- package/src/checks/automatic/aria-conditional-attr.js +32 -14
- package/src/checks/automatic/aria-deprecated-role.js +26 -11
- package/src/checks/automatic/aria-hidden-body.js +48 -23
- package/src/checks/automatic/aria-hidden-focus.js +420 -66
- package/src/checks/automatic/aria-prohibited-attr.js +327 -60
- package/src/checks/automatic/aria-prohibited-children.js +111 -103
- package/src/checks/automatic/aria-required-attr.js +29 -15
- package/src/checks/automatic/aria-required-children.js +44 -24
- package/src/checks/automatic/aria-required-parent.js +64 -35
- package/src/checks/automatic/aria-role-name-present.js +49 -21
- package/src/checks/automatic/aria-roles-valid.js +24 -12
- package/src/checks/automatic/aria-valid-attr-value.js +46 -22
- package/src/checks/automatic/aria-valid-attr.js +19 -5
- package/src/checks/automatic/autocomplete-valid.js +76 -16
- package/src/checks/automatic/avoid-inline-spacing.js +23 -8
- package/src/checks/automatic/binary-control-name-present.js +62 -50
- package/src/checks/automatic/button-name-present.js +54 -24
- package/src/checks/automatic/bypass-blocks-present.js +51 -32
- package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
- package/src/checks/automatic/combobox-name-present.js +40 -45
- package/src/checks/automatic/contrast-computable.js +363 -341
- package/src/checks/automatic/contrast-enhanced.js +489 -466
- package/src/checks/automatic/contrast-minimum.js +488 -465
- package/src/checks/automatic/css-orientation-lock.js +51 -35
- package/src/checks/automatic/definition-list-children-valid.js +46 -25
- package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
- package/src/checks/automatic/dialog-name-present.js +47 -85
- package/src/checks/automatic/dlitem-parent-valid.js +25 -8
- package/src/checks/automatic/duplicate-id-aria.js +28 -9
- package/src/checks/automatic/embed-text-alternative-present.js +88 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
- package/src/checks/automatic/form-control-single-label.js +50 -14
- package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
- package/src/checks/automatic/iframe-focusable-content.js +265 -22
- package/src/checks/automatic/iframe-name-present.js +33 -9
- package/src/checks/automatic/iframe-title-unique.js +32 -9
- package/src/checks/automatic/img-alt-present.js +54 -52
- package/src/checks/automatic/input-image-alt-present.js +141 -112
- package/src/checks/automatic/label-in-name.js +65 -41
- package/src/checks/automatic/language-page-present.js +111 -109
- package/src/checks/automatic/link-in-text-block.js +61 -19
- package/src/checks/automatic/link-name-present.js +47 -14
- package/src/checks/automatic/list-children-valid.js +40 -33
- package/src/checks/automatic/listbox-name-present.js +41 -19
- package/src/checks/automatic/listitem-parent-valid.js +48 -13
- package/src/checks/automatic/menuitem-name-present.js +41 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
- package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
- package/src/checks/automatic/meter-name-present.js +40 -36
- package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
- package/src/checks/automatic/object-text-alternative-present.js +93 -39
- package/src/checks/automatic/option-name-present.js +40 -21
- package/src/checks/automatic/page-title-present.js +19 -6
- package/src/checks/automatic/progressbar-name-present.js +49 -44
- package/src/checks/automatic/role-img-alt-present.js +211 -159
- package/src/checks/automatic/searchbox-name-present.js +41 -19
- package/src/checks/automatic/server-side-image-map-absent.js +27 -11
- package/src/checks/automatic/slider-name-present.js +42 -47
- package/src/checks/automatic/spinbutton-name-present.js +41 -19
- package/src/checks/automatic/summary-name-present.js +39 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
- package/src/checks/automatic/svg-text-alternative-present.js +262 -230
- package/src/checks/automatic/tab-name-present.js +39 -60
- package/src/checks/automatic/table-headers-attr-valid.js +27 -10
- package/src/checks/automatic/table-th-has-data-cells.js +24 -8
- package/src/checks/automatic/target-size-minimum.js +123 -48
- package/src/checks/automatic/td-has-header.js +53 -12
- package/src/checks/automatic/textbox-name-present.js +41 -19
- package/src/checks/automatic/tooltip-name-present.js +39 -18
- package/src/checks/automatic/treeitem-name-present.js +40 -21
- package/src/checks/automatic/valid-lang.js +22 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
- package/src/checks/manual/accesskeys-manual.js +17 -6
- package/src/checks/manual/area-alt-decorative-manual.js +194 -193
- package/src/checks/manual/area-alt-quality-manual.js +184 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
- package/src/checks/manual/aria-text-manual.js +20 -11
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
- package/src/checks/manual/css-hidden-focus.js +375 -169
- package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
- package/src/checks/manual/empty-heading-manual.js +41 -24
- package/src/checks/manual/empty-table-header-manual.js +69 -31
- package/src/checks/manual/focus-order-semantics-manual.js +60 -13
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
- package/src/checks/manual/heading-order-manual.js +50 -8
- package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
- package/src/checks/manual/image-redundant-alt-manual.js +38 -8
- package/src/checks/manual/img-alt-decorative-manual.js +133 -96
- package/src/checks/manual/img-alt-quality-manual.js +178 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
- package/src/checks/manual/label-title-only-manual.js +44 -28
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
- package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
- package/src/checks/manual/landmark-one-main-manual.js +38 -43
- package/src/checks/manual/landmark-unique-manual.js +78 -67
- package/src/checks/manual/link-name-quality-manual.js +45 -12
- package/src/checks/manual/media-transcript-present-manual.js +37 -22
- package/src/checks/manual/meta-viewport-large-manual.js +19 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
- package/src/checks/manual/p-as-heading-manual.js +24 -7
- package/src/checks/manual/page-has-heading-one-manual.js +42 -32
- package/src/checks/manual/page-title-patterns-manual.js +80 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
- package/src/checks/manual/region-manual.js +244 -60
- package/src/checks/manual/scope-attr-valid-manual.js +13 -4
- package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
- package/src/checks/manual/skip-link-manual.js +42 -18
- package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
- package/src/checks/manual/tabindex-manual.js +13 -4
- package/src/checks/manual/table-duplicate-name-manual.js +22 -11
- package/src/checks/manual/table-fake-caption-manual.js +48 -10
- package/src/checks/manual/video-caption-manual.js +17 -4
- package/src/checks/manual-review.js +58 -12
- package/src/core.js +41705 -29650
- package/src/index.js +2 -0
- package/src/report.js +109 -47
- package/src/sarif.js +190 -0
- package/surea11y.browser.js +37774 -0
- package/bin/core.js +0 -348
- package/docs/CLI.md +0 -75
- package/src/catalogs/composites.wcag.js +0 -490
- package/src/checks/rules-and-tags.full.csv +0 -19
- package/src/checks/rules-and-tags.full.json +0 -259
- package/src/core/aria-helpers.js +0 -970
- package/src/core/contrast-helpers.js +0 -1147
- package/src/core/dom-helpers.js +0 -4235
- package/src/core/dom-runner.js +0 -671
- package/src/core/frame-messaging.js +0 -210
- package/src/core/frame-scan.js +0 -178
- package/src/core/rollup-composites.js +0 -135
- package/src/core/rule-meta.js +0 -159
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -923
- package/src/i18n/fr.js +0 -844
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -55
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
package/README.md
CHANGED
|
@@ -1,33 +1,57 @@
|
|
|
1
1
|
# @surea11y/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@surea11y/core#gh-dark-mode-only)
|
|
4
|
+
[](https://www.npmjs.com/package/@surea11y/core#gh-light-mode-only)
|
|
5
|
+
[](https://www.npmjs.com/package/@surea11y/core)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](LICENSE)
|
|
4
8
|
|
|
5
|
-
|
|
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.
|
|
9
|
+
> **Accessibility testing that tells you what it can't tell you.**
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
reporting tools.
|
|
11
|
+
surea11y is an accessibility engine for teams that need to know what automated
|
|
12
|
+
testing *can't* establish. It reports findings, non-findings, and — unusually —
|
|
13
|
+
explicit uncertainty, so results are auditable rather than reassuring.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
*Sure* means certainty about what is known, and honesty about what isn't.
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
results
|
|
17
|
+
It runs against either static HTML or fully rendered browser pages, producing
|
|
18
|
+
deterministic, standards-traceable results suitable for local development,
|
|
19
|
+
automated testing and CI/CD pipelines.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Unlike browser extensions or cloud-based services, surea11y is a library-first
|
|
22
|
+
project. You install it, run it where your code runs, and receive structured
|
|
23
|
+
results that can be consumed by people, scripts or reporting tools.
|
|
23
24
|
|
|
24
|
-
|
|
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.
|
|
25
|
+
## What automated testing can and cannot do
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
Automated tools are commonly reckoned to catch somewhere around a third of WCAG
|
|
28
|
+
issues. The remainder require human judgement. That ceiling is a property of
|
|
29
|
+
static analysis itself, not a gap in any particular tool.
|
|
30
|
+
|
|
31
|
+
surea11y's answer is to be explicit about which side of that line every result
|
|
32
|
+
falls on. Each rule makes a single deterministic decision:
|
|
33
|
+
|
|
34
|
+
- **`fail`** — a violation provable from the DOM. Reserved for objective,
|
|
35
|
+
normative cases.
|
|
36
|
+
- **`pass`** — this rule's specific condition is met. Not a claim that the page
|
|
37
|
+
is accessible.
|
|
38
|
+
- **`cantTell`** — a human has to decide this, and the result says what was
|
|
39
|
+
ambiguous.
|
|
40
|
+
- **`notApplicable`** — the rule's precondition isn't present.
|
|
41
|
+
|
|
42
|
+
`cantTell` is the point of the project. An engine that quietly discards what it
|
|
43
|
+
cannot determine produces a shorter report and a false sense of coverage.
|
|
44
|
+
|
|
45
|
+
## What this engine does not detect
|
|
46
|
+
|
|
47
|
+
Keyboard traps, reflow and clipping at 400% zoom, anything that only exists
|
|
48
|
+
after a click or an async load, and judgement calls such as whether a heading is
|
|
49
|
+
meaningful — these lie outside what a static DOM scan can establish. Each is a
|
|
50
|
+
reasoned decision rather than an oversight.
|
|
51
|
+
|
|
52
|
+
[`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md) lists them in full with the
|
|
53
|
+
reasoning for each. A `pass` from this engine — or from any automated tool — is
|
|
54
|
+
never a substitute for the manual review WCAG itself requires.
|
|
31
55
|
|
|
32
56
|
### Key principles
|
|
33
57
|
|
|
@@ -44,7 +68,9 @@ automated quality gates.
|
|
|
44
68
|
- **Extensible.** Add custom rules, register policies and filter scans
|
|
45
69
|
by rule IDs, tags or WCAG version.
|
|
46
70
|
- **Localized reporting.** Human-readable messages can be translated
|
|
47
|
-
without affecting machine-readable data.
|
|
71
|
+
without affecting machine-readable data. Ships with `en`, `fr`, `de`,
|
|
72
|
+
and `es` today — see [`docs/I18N.md`](./docs/I18N.md) to use one or
|
|
73
|
+
contribute another.
|
|
48
74
|
|
|
49
75
|
---
|
|
50
76
|
|
|
@@ -92,6 +118,26 @@ For a detailed comparison of both execution models, see
|
|
|
92
118
|
|
|
93
119
|
---
|
|
94
120
|
|
|
121
|
+
## Which package do I need?
|
|
122
|
+
|
|
123
|
+
surea11y is a family of packages sharing one engine. Install the one that
|
|
124
|
+
matches how you test — each pulls in `@surea11y/core` for you.
|
|
125
|
+
|
|
126
|
+
| I want to… | Install |
|
|
127
|
+
|---|---|
|
|
128
|
+
| Add accessibility checks to **Playwright** tests | [`@surea11y/playwright`](https://github.com/SureA11y/playwright#readme) |
|
|
129
|
+
| …**Puppeteer** | [`@surea11y/puppeteer`](https://github.com/SureA11y/puppeteer#readme) |
|
|
130
|
+
| …**Selenium** | [`@surea11y/selenium`](https://github.com/SureA11y/selenium#readme) |
|
|
131
|
+
| …**Cypress** | [`@surea11y/cypress`](https://github.com/SureA11y/cypress#readme) |
|
|
132
|
+
| …**WebdriverIO** | [`@surea11y/webdriverio`](https://github.com/SureA11y/webdriverio#readme) |
|
|
133
|
+
| Assert in **Jest or Vitest** component tests | [`@surea11y/test-matchers`](https://github.com/SureA11y/test-matchers#readme) |
|
|
134
|
+
| Scan static HTML from a **terminal or CI pipeline** | [`@surea11y/cli`](https://github.com/SureA11y/cli#readme) |
|
|
135
|
+
| Run the engine against **a DOM I already have** | `@surea11y/core` (this package) |
|
|
136
|
+
|
|
137
|
+
The rest of this README covers `@surea11y/core` itself.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
95
141
|
## Installation
|
|
96
142
|
|
|
97
143
|
Install the core package from npm:
|
|
@@ -100,10 +146,13 @@ Install the core package from npm:
|
|
|
100
146
|
npm install @surea11y/core
|
|
101
147
|
```
|
|
102
148
|
|
|
103
|
-
The core engine has
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
149
|
+
The core engine has **zero runtime dependencies**. Installing it pulls
|
|
150
|
+
nothing else into your tree, which keeps it lightweight and suitable for
|
|
151
|
+
embedding into your own tooling.
|
|
152
|
+
|
|
153
|
+
The engine needs a DOM to read, but it never creates one — you supply it,
|
|
154
|
+
whether that's jsdom, a Playwright page, or the live document in a
|
|
155
|
+
browser. That is why nothing is installed on your behalf.
|
|
107
156
|
|
|
108
157
|
---
|
|
109
158
|
|
|
@@ -111,18 +160,21 @@ suitable for embedding into your own tooling.
|
|
|
111
160
|
|
|
112
161
|
### CLI
|
|
113
162
|
|
|
114
|
-
The CLI
|
|
163
|
+
The CLI ships as a separate package, [`@surea11y/cli`](https://www.npmjs.com/package/@surea11y/cli),
|
|
164
|
+
so that installing the engine never pulls a DOM implementation into
|
|
165
|
+
projects that already have one:
|
|
115
166
|
|
|
116
167
|
```bash
|
|
117
|
-
npx @surea11y/
|
|
118
|
-
npx @surea11y/
|
|
168
|
+
npx @surea11y/cli scan ./index.html
|
|
169
|
+
npx @surea11y/cli scan https://example.com/
|
|
119
170
|
```
|
|
120
171
|
|
|
121
172
|
The CLI analyses static HTML. It does not execute client-side
|
|
122
173
|
JavaScript, making it ideal for static sites and server-rendered
|
|
123
174
|
applications.
|
|
124
175
|
|
|
125
|
-
For available options, exit codes and advanced usage, see
|
|
176
|
+
For available options, exit codes and advanced usage, see the
|
|
177
|
+
[CLI documentation](https://github.com/SureA11y/cli#readme).
|
|
126
178
|
|
|
127
179
|
---
|
|
128
180
|
|
|
@@ -133,7 +185,12 @@ surea11y exposes two entry points depending on where your code executes.
|
|
|
133
185
|
#### Node.js + jsdom
|
|
134
186
|
|
|
135
187
|
Use `runDomRulesInPage()` when your application already has a DOM
|
|
136
|
-
available through jsdom.
|
|
188
|
+
available through jsdom. jsdom is not a dependency of this package, so
|
|
189
|
+
install it alongside if you don't already have it:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm install jsdom
|
|
193
|
+
```
|
|
137
194
|
|
|
138
195
|
```js
|
|
139
196
|
const { JSDOM } = require("jsdom");
|
|
@@ -198,6 +255,66 @@ For complete integration examples, see `docs/INTEGRATION.md`.
|
|
|
198
255
|
|
|
199
256
|
---
|
|
200
257
|
|
|
258
|
+
#### Standalone browser bundle
|
|
259
|
+
|
|
260
|
+
For a page that isn't driven by an automation framework at all — a manual
|
|
261
|
+
QA pass, a bookmarklet, a browser extension — the package also ships a
|
|
262
|
+
self-contained bundle that needs no `require`, no bundler, and no build
|
|
263
|
+
step:
|
|
264
|
+
|
|
265
|
+
```html
|
|
266
|
+
<script src="node_modules/@surea11y/core/surea11y.browser.js"></script>
|
|
267
|
+
<script>
|
|
268
|
+
const result = a11ycore.runa11yCoreInPage(
|
|
269
|
+
location.href, // pageUrl
|
|
270
|
+
null, // contextSelector
|
|
271
|
+
{}, // engineOptions
|
|
272
|
+
null // runOnly
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
console.log(result.checksResults.filter(r => r.outcome === "fail"));
|
|
276
|
+
</script>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Loading `surea11y.browser.js` defines a single global, `a11ycore`, exposing
|
|
280
|
+
the same `runa11yCoreInPage` function described above — calling it runs a
|
|
281
|
+
real scan against the page it's loaded into and returns the same result
|
|
282
|
+
shape documented in [Understanding the Results](#understanding-the-results).
|
|
283
|
+
|
|
284
|
+
`contextSelector`, `engineOptions`, and `runOnly` are the same three
|
|
285
|
+
arguments described throughout this README and `docs/ENGINE_OPTIONS.md` —
|
|
286
|
+
nothing about calling the engine changes just because it's loaded this
|
|
287
|
+
way. A scan scoped to one region, filtered to a specific WCAG level, with
|
|
288
|
+
a couple of known-noisy selectors excluded, looks like this:
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<script src="node_modules/@surea11y/core/surea11y.browser.js"></script>
|
|
292
|
+
<script>
|
|
293
|
+
const result = a11ycore.runa11yCoreInPage(
|
|
294
|
+
location.href,
|
|
295
|
+
"#main", // contextSelector: scan only this region
|
|
296
|
+
{
|
|
297
|
+
excludeSelectors: ["#cookie-banner", ".intercom-launcher"],
|
|
298
|
+
contrast: { mode: "auditorAssist" } // trade some false-positive protection for more findings
|
|
299
|
+
},
|
|
300
|
+
{ tags: ["wcag2a", "wcag2aa"] } // runOnly: WCAG 2.0 A/AA rules only
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
console.log(result.checksResults.filter(r => r.outcome === "fail"));
|
|
304
|
+
</script>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
This bundle is generated from the same rule sources as the rest of the
|
|
308
|
+
engine (`npm run build` regenerates it alongside `src/core.js`), so its
|
|
309
|
+
behavior never drifts from the library's. It intentionally exposes only
|
|
310
|
+
`runa11yCoreInPage` — cross-frame scanning
|
|
311
|
+
(`runa11yCoreAcrossFrames`/`a11yCoreEnableFrameResponder`) requires the
|
|
312
|
+
embedded frame to also load the engine and opt in, which doesn't fit a
|
|
313
|
+
single dropped-in `<script>` tag; reach for the npm package directly if
|
|
314
|
+
you need that.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
201
318
|
## Understanding the Results
|
|
202
319
|
|
|
203
320
|
Every scan returns a structured JSON document designed for both
|
|
@@ -271,9 +388,10 @@ and progressively explore more advanced features.
|
|
|
271
388
|
|---|---|
|
|
272
389
|
| `docs/OUTPUT_SCHEMA.md` | Complete description of every field returned by the engine. |
|
|
273
390
|
| `docs/API_STABILITY.md` | Semver guarantees on the result shape, and the rule-ID deprecation policy. |
|
|
274
|
-
| `docs/CLI.md` | CLI commands, options, exit codes and examples. |
|
|
275
391
|
| `docs/BASELINE.md` | CI baseline/allowlist: gate builds only on new violations. |
|
|
276
392
|
| `docs/REPORT.md` | Self-contained HTML report: browsable summary, WCAG rollup, filterable occurrence table. |
|
|
393
|
+
| `docs/SARIF.md` | SARIF 2.1.0 report for GitHub Code Scanning and other SARIF dashboards. |
|
|
394
|
+
| `docs/CI_INTEGRATIONS.md` | GitHub Actions and Bitbucket Pipelines templates wrapping the CLI. |
|
|
277
395
|
| `docs/ENGINE_OPTIONS.md` | Configuration, filtering, policies and localization. |
|
|
278
396
|
| `docs/INTEGRATION.md` | Using surea11y with jsdom, Playwright, Puppeteer, Selenium, Cypress and other drivers. |
|
|
279
397
|
| `docs/BINDING_AUTHORS_GUIDE.md` | Building new framework integrations on top of the engine. |
|
|
@@ -341,8 +459,7 @@ The repository is organised so that the accessibility engine, rule
|
|
|
341
459
|
implementations and supporting infrastructure remain clearly separated.
|
|
342
460
|
|
|
343
461
|
```text
|
|
344
|
-
|
|
345
|
-
core.js # CLI entry point
|
|
462
|
+
surea11y.browser.js # Generated standalone browser bundle
|
|
346
463
|
|
|
347
464
|
src/
|
|
348
465
|
index.js # Public API
|
|
@@ -421,12 +538,35 @@ supported versions and the preferred disclosure process.
|
|
|
421
538
|
|
|
422
539
|
---
|
|
423
540
|
|
|
541
|
+
## Versioning & stability
|
|
542
|
+
|
|
543
|
+
`@surea11y/core` follows [semantic versioning](https://semver.org/). The result
|
|
544
|
+
shape is a written contract — see [`docs/API_STABILITY.md`](docs/API_STABILITY.md)
|
|
545
|
+
for exactly which fields are covered by semver, what triggers a patch/minor/major
|
|
546
|
+
bump, the release cadence, and the rule-ID deprecation policy.
|
|
547
|
+
|
|
548
|
+
In short: patch and minor releases are always backward-compatible, so a consumer
|
|
549
|
+
pinned to a `^1.y.0` range is never broken by an upgrade within the `1.x` line.
|
|
550
|
+
Correctness fixes ship as patches when ready; feature work is batched into
|
|
551
|
+
periodic minors; breaking changes are reserved for major versions and are rare by
|
|
552
|
+
design.
|
|
553
|
+
|
|
554
|
+
## Maintainer
|
|
555
|
+
|
|
556
|
+
surea11y is built and maintained by [Jorge Rumoroso](https://github.com/rumoroso).
|
|
557
|
+
|
|
558
|
+
Bug reports and rule proposals are welcome via
|
|
559
|
+
[issues](https://github.com/SureA11y/core/issues). For security disclosures see
|
|
560
|
+
[`SECURITY.md`](./SECURITY.md).
|
|
561
|
+
|
|
424
562
|
## License
|
|
425
563
|
|
|
426
|
-
This project is released under the
|
|
564
|
+
This project is released under the Mozilla Public License 2.0 (MPL-2.0).
|
|
427
565
|
|
|
428
566
|
See the accompanying `LICENSE` file for the complete license text.
|
|
429
567
|
|
|
568
|
+
MPL-2.0 is file-level copyleft: it applies to `@surea11y/core`'s own source files, not to code that merely depends on it. A project that installs `@surea11y/core` as a normal package dependency and imports its public API — without copying or modifying this repository's source files — is unaffected by MPL-2.0 and may keep its own license (including a permissive one like MIT).
|
|
569
|
+
|
|
430
570
|
---
|
|
431
571
|
|
|
432
572
|
## Final Notes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
// Redirects the pre-1.4.0 `npx @surea11y/core scan ...` that older docs still
|
|
7
|
+
// show. Not named `surea11y`: that belongs to @surea11y/cli, and core is a
|
|
8
|
+
// transitive dependency of every binding, so both would collide in one .bin.
|
|
9
|
+
|
|
10
|
+
process.stderr.write(
|
|
11
|
+
`The surea11y CLI is no longer part of @surea11y/core (moved in 1.4.0).
|
|
12
|
+
|
|
13
|
+
npx @surea11y/cli scan <file-or-url>
|
|
14
|
+
|
|
15
|
+
Install it with: npm install --save-dev @surea11y/cli
|
|
16
|
+
Docs: https://github.com/SureA11y/cli
|
|
17
|
+
`
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
process.exit(2);
|
package/docs/API_STABILITY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# API stability & versioning
|
|
2
2
|
|
|
3
|
-
`@surea11y/core` has real downstream consumers today
|
|
3
|
+
`@surea11y/core` has real downstream consumers today: 5 first-party framework bindings (Playwright, Puppeteer, Selenium, WebdriverIO, Cypress) published to npm, plus a Jest/Vitest matcher (`@surea11y/test-matchers`, `toHaveNoA11yViolations()`) — all pinned to a `^1.1.0`-style semver range. Until now, "what counts as a breaking change" was implicit — discoverable only by reading source, not written down anywhere. This document makes that contract explicit.
|
|
4
4
|
|
|
5
5
|
## Stable fields (covered by semver)
|
|
6
6
|
|
|
@@ -13,6 +13,22 @@ Removing, renaming, or changing the type/meaning of any of these is a **major**
|
|
|
13
13
|
|
|
14
14
|
This list is deliberately not a new, invented guarantee — it codifies what the 6 real consumers above (and `docs/OUTPUT_SCHEMA.md`'s own worked examples) already depend on today, either directly or as documented shape.
|
|
15
15
|
|
|
16
|
+
## Package entry points (covered by semver)
|
|
17
|
+
|
|
18
|
+
Since 1.4.0 the package declares an explicit `exports` map. These are the only importable paths, and removing or repointing one is a **major** bump:
|
|
19
|
+
|
|
20
|
+
| Specifier | Resolves to | Contents |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `@surea11y/core` | `src/index.js` | the full engine surface (`runDomRulesInPage`, `runa11yCoreInPage`, catalog accessors, …) |
|
|
23
|
+
| `@surea11y/core/baseline` | `src/baseline.js` | `buildBaselineEntries()`, `matchBaseline()` |
|
|
24
|
+
| `@surea11y/core/report` | `src/report.js` | `renderHtmlReport()` |
|
|
25
|
+
| `@surea11y/core/sarif` | `src/sarif.js` | `renderSarifReport()` |
|
|
26
|
+
| `@surea11y/core/browser` | `surea11y.browser.js` | the standalone browser bundle, for bundlers that resolve it as a module |
|
|
27
|
+
|
|
28
|
+
Anything **not** in that table — `src/core/*`, `src/checks/*`, `src/i18n/*`, `src/policy/*`, and the generated `src/core.js` itself — is internal. Before 1.4.0 there was no `exports` map, so those paths were technically reachable via deep `require()`; they were never documented as public and are no longer resolvable. The `<script src="node_modules/@surea11y/core/surea11y.browser.js">` form documented in the README is a filesystem path, not module resolution, and is unaffected.
|
|
29
|
+
|
|
30
|
+
Declaring this map is what lets the engine's internal file layout change without a major bump. Note that `src/checks/*` is still *shipped* (the generated bundle `require()`s it at runtime) — shipped is not the same as public.
|
|
31
|
+
|
|
16
32
|
## Explicitly unstable (not covered by semver)
|
|
17
33
|
|
|
18
34
|
- `perfStats` and `ruleTimings` — internal timing/debug counters, only present when `engineOptions.perfStats`/`.profileRules` is set. Shape not covered by this document.
|
|
@@ -27,6 +43,16 @@ This list is deliberately not a new, invented guarantee — it codifies what the
|
|
|
27
43
|
|
|
28
44
|
`engine.schemaVersion` has been `"1.0.0"` since the engine's first release and has never needed a bump — nothing has changed a stable field's shape yet. Adding the `deprecated`/`deprecation` meta fields described below is purely additive (new optional fields, ignored safely by anything not looking for them), so it does **not** warrant a schema bump either — this is the policy's first real application.
|
|
29
45
|
|
|
46
|
+
## Release cadence
|
|
47
|
+
|
|
48
|
+
The version number is the contract — not a measure of how much has changed or how often. surea11y follows semver strictly, so what a bump *means* is fixed regardless of how frequently they happen:
|
|
49
|
+
|
|
50
|
+
- **Patch (`x.y.Z`)** — rule-correctness fixes and documentation updates. Released promptly, as needed, rather than held back; always safe to adopt within a major line.
|
|
51
|
+
- **Minor (`x.Y.0`)** — additive, backward-compatible work: new rules, new locales, new `engineOptions`, new output formats. Batched into periodic releases rather than shipped one change at a time.
|
|
52
|
+
- **Major (`X.0.0`)** — a breaking change to a stable field (see above). Rare by design; the entire point of the stable-fields list is to keep these infrequent and well-signposted.
|
|
53
|
+
|
|
54
|
+
Because every `1.x` release is backward-compatible, a consumer pinned to a `^1.y.0` range is never broken by an upgrade within the line — so a steady stream of patch/minor releases reflects active maintenance and prompt fixes, not instability. Frequency of releases is not a signal of churn; a change to a **major** version is.
|
|
55
|
+
|
|
30
56
|
## Rule-ID deprecation policy
|
|
31
57
|
|
|
32
58
|
A rule can be marked deprecated in its own `meta`:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Binding authors' guide
|
|
2
2
|
|
|
3
|
-
Written for whoever builds the *next* framework binding on top of
|
|
3
|
+
Written for whoever builds the *next* framework binding on top of `@surea11y/core` (Puppeteer, Cypress, Selenium, WebdriverIO, whatever comes after) — not for someone consuming a binding, and not for someone calling the engine directly. If that's you, see [`INTEGRATION.md`](./INTEGRATION.md) instead.
|
|
4
4
|
|
|
5
|
-
The first real binding,
|
|
5
|
+
The first real binding, `@surea11y/playwright` (a sibling project, not part of this repo), has already worked through most of the design questions a new binding hits. This doc exists so the next one doesn't have to re-derive them — it's a checklist and a map of "what the engine already gives you for free" vs. "what every binding has to build itself," backed by what that binding actually did, not theory.
|
|
6
6
|
|
|
7
7
|
## Core-engine vs. binding-layer: know which side you're on
|
|
8
8
|
|
|
@@ -10,19 +10,19 @@ Some engine-parity features (relative to other established engines) are **engine
|
|
|
10
10
|
|
|
11
11
|
**Already engine-level, works the moment you call `runa11yCoreInPage`/`runDomRulesInPage` — no binding code needed:**
|
|
12
12
|
- All rule execution, WCAG SC mapping, composite rollups.
|
|
13
|
-
- `runOnly`/`engineOptions.rules`/`.tags`/`.tests` rule selection — including [WCAG-version filtering](./ENGINE_OPTIONS.md#filtering-by-wcag-version-21-vs-22) (`wcag21a`/`wcag22aa`-style tags): if your binding has any kind of `.withTags()`/`.options()` passthrough that forwards `runOnly`/`engineOptions` generically, WCAG-version filtering already works through it with zero extra code — just document the tag vocabulary for your users, the way
|
|
13
|
+
- `runOnly`/`engineOptions.rules`/`.tags`/`.tests` rule selection — including [WCAG-version filtering](./ENGINE_OPTIONS.md#filtering-by-wcag-version-21-vs-22) (`wcag21a`/`wcag22aa`-style tags): if your binding has any kind of `.withTags()`/`.options()` passthrough that forwards `runOnly`/`engineOptions` generically, WCAG-version filtering already works through it with zero extra code — just document the tag vocabulary for your users, the way `@surea11y/playwright`'s README does.
|
|
14
14
|
- `structuralPath` on every `fail`/`cantTell` occurrence — if your binding passes occurrences through unreshaped (don't strip fields you don't recognize), this reaches your consumers automatically.
|
|
15
15
|
- `engineOptions.customRules` — runtime rule registration. Works through a generic `engineOptions` passthrough too, **but** see the caveat below: if your binding crosses a serialization boundary (see next section), your consumers must pass `runInPage`/`applicability` as `fn.toString()` source, not a live function. Worth a dedicated `.withCustomRules([...])` convenience method for ergonomics, but not required for the feature to work.
|
|
16
|
-
- Cross-frame scanning **if** your driver reaches every frame itself already (Puppeteer/Cypress/Selenium all can, via CDP or equivalent) — you don't need `runa11yCoreAcrossFrames`/`a11yCoreEnableFrameResponder` at all. Just call the engine once per frame your driver already gives you and merge the results yourself (see
|
|
16
|
+
- Cross-frame scanning **if** your driver reaches every frame itself already (Puppeteer/Cypress/Selenium all can, via CDP or equivalent) — you don't need `runa11yCoreAcrossFrames`/`a11yCoreEnableFrameResponder` at all. Just call the engine once per frame your driver already gives you and merge the results yourself (see `@surea11y/playwright`'s `.frames(true)`, which does exactly this — no engine change was needed for it). The `postMessage`-based cross-frame functions exist specifically for the *no-automation-driver* case (a plain injected script) and are the wrong tool for a driver-based binding.
|
|
17
17
|
|
|
18
18
|
**Binding-layer — your binding has to build these itself, the engine won't:**
|
|
19
|
-
- **Element references.** The engine returns `selector`/`structuralPath` strings, never a live handle — it has no concept of your driver's element-reference type. Resolve `occurrences[i].selector` back to a real handle yourself (Playwright's approach: `page.evaluateHandle` instead of `page.evaluate`, then `elementHandle.$(selector)` per occurrence — see `.elementRef(true)` in
|
|
20
|
-
- **Result verbosity/reporter filtering.** The engine deliberately always returns every rule's outcome, including `pass`/`notApplicable` — "not a violations-only list" is a stated engine design choice (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md)), not an oversight to work around. If your consumers want a trimmed view for CI-scale output, that's a post-filter your binding adds (
|
|
19
|
+
- **Element references.** The engine returns `selector`/`structuralPath` strings, never a live handle — it has no concept of your driver's element-reference type. Resolve `occurrences[i].selector` back to a real handle yourself (Playwright's approach: `page.evaluateHandle` instead of `page.evaluate`, then `elementHandle.$(selector)` per occurrence — see `.elementRef(true)` in `@surea11y/playwright`).
|
|
20
|
+
- **Result verbosity/reporter filtering.** The engine deliberately always returns every rule's outcome, including `pass`/`notApplicable` — "not a violations-only list" is a stated engine design choice (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md)), not an oversight to work around. If your consumers want a trimmed view for CI-scale output, that's a post-filter your binding adds (`@surea11y/playwright`'s `.reportOnly(['fail','cantTell'])` is a simple array-filter over the full result — no engine change).
|
|
21
21
|
- **Formatted failure output for your framework's own assertion/reporting style** (e.g. Playwright/Jest-style multi-line failure messages). The engine's raw result is framework-agnostic on purpose; shaping it into "what shows up in a failed test's stack trace" is squarely binding territory.
|
|
22
22
|
|
|
23
23
|
## The serialization-boundary caveat
|
|
24
24
|
|
|
25
|
-
If your binding drives a *separate JS realm* (a browser page/tab is a different realm than your Node test process — this is Playwright/Puppeteer/Selenium's situation, not Cypress's, since Cypress test code already runs in-browser), anything you hand to `page.evaluate()`-equivalent gets structurally cloned/JSON-serialized. **Live functions do not survive that boundary.** This bit
|
|
25
|
+
If your binding drives a *separate JS realm* (a browser page/tab is a different realm than your Node test process — this is Playwright/Puppeteer/Selenium's situation, not Cypress's, since Cypress test code already runs in-browser), anything you hand to `page.evaluate()`-equivalent gets structurally cloned/JSON-serialized. **Live functions do not survive that boundary.** This bit `@surea11y/playwright` in two places:
|
|
26
26
|
1. `runa11yCoreInPage` itself is designed around this — it's fully self-contained (`.toString()`-serializable, no closure over outer scope) specifically so it can be reconstructed from source inside the page realm.
|
|
27
27
|
2. `engineOptions.customRules[].runInPage`/`.applicability` accept a function-source string for exactly this reason — a binding crossing this boundary must tell its consumers to pass `fn.toString()`, not `fn`. Document this prominently; it's an easy trap (a function looks like it should just work as an argument until it silently fails to serialize).
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ If your binding runs in the *same* realm as the page (a browser extension conten
|
|
|
30
30
|
|
|
31
31
|
## Things to check before shipping a new binding
|
|
32
32
|
|
|
33
|
-
A short list, derived from what the audit pass on
|
|
33
|
+
A short list, derived from what the audit pass on `@surea11y/playwright` actually found missing on a first pass (per its own `ROADMAP.md`) — worth checking explicitly rather than assuming your binding's generic passthrough covers them:
|
|
34
34
|
- [ ] Combinations of your own filtering methods behave sanely together (e.g. include+exclude on the same ID, tag-include + tag-exclude on the same tag) — these interact through the engine's `includeMode`/exclude-always-wins semantics ([`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md)), test them explicitly rather than assuming.
|
|
35
35
|
- [ ] `structuralPath` and `customRules` still work correctly when combined with whatever binding-layer features you build (verbosity filtering, element refs, per-frame scanning) — a filter applied after the fact should never silently drop fields a consumer expects on a surviving occurrence.
|
|
36
36
|
- [ ] If you support cross-frame scanning via your own driver, confirm each frame's result gets the same normalization (selector/structuralPath/severity) as a single-document scan — don't let a "per-frame" code path silently skip the shared result-shaping logic.
|
|
@@ -38,4 +38,4 @@ A short list, derived from what the audit pass on `surea11y-playwright` actually
|
|
|
38
38
|
|
|
39
39
|
## A known engine-side tradeoff worth knowing about
|
|
40
40
|
|
|
41
|
-
`src/core.js` is not small (~3.1MB as of 2026-07-22) because the bundler-free, no-driver-context functions (`runa11yCoreInPage`, `runa11yCoreAcrossFrames`, `a11yCoreEnableFrameResponder`) each carry their own complete self-contained copy of the rule catalog. If your binding only ever uses `require('@surea11y/core')` in Node and injects `runa11yCoreInPage.toString()` into the page (the same pattern
|
|
41
|
+
`src/core.js` is not small (~3.1MB as of 2026-07-22) because the bundler-free, no-driver-context functions (`runa11yCoreInPage`, `runa11yCoreAcrossFrames`, `a11yCoreEnableFrameResponder`) each carry their own complete self-contained copy of the rule catalog. If your binding only ever uses `require('@surea11y/core')` in Node and injects `runa11yCoreInPage.toString()` into the page (the same pattern `@surea11y/playwright` uses), your actual browser-injected payload is unaffected by this — only your Node-side `require()` footprint grows. Worth knowing if your binding's own package size matters to your consumers.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# CI/CD pipeline integrations
|
|
2
|
+
|
|
3
|
+
Ready-to-paste templates wrapping the [CLI](https://github.com/SureA11y/cli#readme) (`npx @surea11y/cli scan ...`) in GitHub Actions and Bitbucket Pipelines. If you're calling the library directly from your own Node script instead of the CLI, see [`INTEGRATION.md`](./INTEGRATION.md#ci-gating-a-build-on-the-result) instead — this page is specifically about the CLI as a pipeline step.
|
|
4
|
+
|
|
5
|
+
All of these rely on the CLI's own exit codes (`0` clean, `1` at least one — or one *new*, with `--baseline` — `fail` outcome, `2` a usage/scan error) to gate the pipeline; no extra scripting is required for basic pass/fail gating.
|
|
6
|
+
|
|
7
|
+
## GitHub Actions
|
|
8
|
+
|
|
9
|
+
### Basic: gate on exit code
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
name: Accessibility scan
|
|
13
|
+
on: [pull_request]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
a11y-scan:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: 20
|
|
23
|
+
- run: npm ci && npm run build # produce whatever static HTML you're scanning
|
|
24
|
+
- run: npx @surea11y/cli scan ./dist/index.html
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This fails the job the moment any `fail` outcome is found. For an existing site with pre-existing violations, see the baseline variant below instead of disabling the step.
|
|
28
|
+
|
|
29
|
+
### With a baseline (existing site, gate only on new violations)
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
# Once, locally: record every current fail occurrence, commit the file.
|
|
33
|
+
npx @surea11y/cli scan ./dist/index.html --write-baseline a11y-baseline.json
|
|
34
|
+
git add a11y-baseline.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
- run: npm ci && npm run build
|
|
39
|
+
- run: npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See [`BASELINE.md`](./BASELINE.md) for what counts as "known" vs. "new", and how to regenerate the file as violations get fixed.
|
|
43
|
+
|
|
44
|
+
### Uploading SARIF to GitHub Code Scanning
|
|
45
|
+
|
|
46
|
+
`upload-sarif` needs `security-events: write` permission and does not fail the job itself — pair it with a separate gating step (or `continue-on-error` + your own check) if you also want the build to fail on new violations.
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
name: Accessibility scan
|
|
50
|
+
on: [pull_request]
|
|
51
|
+
|
|
52
|
+
permissions:
|
|
53
|
+
contents: read
|
|
54
|
+
security-events: write
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
a11y-scan:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
- uses: actions/setup-node@v4
|
|
62
|
+
with:
|
|
63
|
+
node-version: 20
|
|
64
|
+
- run: npm ci && npm run build
|
|
65
|
+
- name: Scan (report, don't fail the job here)
|
|
66
|
+
run: npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json --sarif results.sarif
|
|
67
|
+
continue-on-error: true
|
|
68
|
+
id: scan
|
|
69
|
+
- name: Upload SARIF to Code Scanning
|
|
70
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
71
|
+
with:
|
|
72
|
+
sarif_file: results.sarif
|
|
73
|
+
- name: Fail the job on new violations
|
|
74
|
+
if: steps.scan.outcome == 'failure'
|
|
75
|
+
run: exit 1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See [`SARIF.md`](./SARIF.md) for the output format and a known limitation: a scan of a **local file** in your checkout gets inline annotations in the Code Scanning UI; a scan of a **live URL** doesn't (SARIF/Code Scanning can only associate a finding with a real file in the repository).
|
|
79
|
+
|
|
80
|
+
## Bitbucket Pipelines
|
|
81
|
+
|
|
82
|
+
Bitbucket Pipelines has no built-in SARIF-consuming dashboard equivalent to GitHub Code Scanning, so the CLI's exit code (plus, optionally, `--html` as a downloadable pipeline artifact) is the practical integration point rather than `--sarif`.
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
pipelines:
|
|
86
|
+
pull-requests:
|
|
87
|
+
'**':
|
|
88
|
+
- step:
|
|
89
|
+
name: Accessibility scan
|
|
90
|
+
image: node:20
|
|
91
|
+
script:
|
|
92
|
+
- npm ci
|
|
93
|
+
- npm run build
|
|
94
|
+
- npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json --html a11y-report.html
|
|
95
|
+
artifacts:
|
|
96
|
+
- a11y-report.html
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The step fails the pipeline on the CLI's exit code exactly like any other `script` entry; `a11y-report.html` (see [`REPORT.md`](./REPORT.md)) is attached as a downloadable build artifact so a reviewer can open it without re-running the scan locally.
|
|
100
|
+
|
|
101
|
+
## Free-tier/private-repo minute limits
|
|
102
|
+
|
|
103
|
+
If your pipeline provider's free tier is minute-limited (Bitbucket Pipelines' free tier is 50 build-minutes/month on private workspaces, for example), a `jsdom`-based scan of static/server-rendered HTML (what the CLI does) is far cheaper than driving a real browser — see [the CLI docs](https://github.com/SureA11y/cli/blob/main/docs/CLI.md#what-it-can-and-cant-scan) for what that trades away (no client-rendered content, no real CSS layout).
|
package/docs/ENGINE_OPTIONS.md
CHANGED
|
@@ -224,6 +224,8 @@ See the option-by-option table above for anything not shown here, and the `custo
|
|
|
224
224
|
|
|
225
225
|
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.
|
|
226
226
|
|
|
227
|
+
Calling the library directly is one way in; the CLI also exposes this via `--custom-rules <path>` (a local file, loaded once per scan) — see [the CLI docs](https://github.com/SureA11y/cli/blob/main/docs/CLI.md#custom-rules).
|
|
228
|
+
|
|
227
229
|
A descriptor has the *same shape as an internal rule module's own export* — if you already know how to write a rule file for this engine, you already know this API:
|
|
228
230
|
|
|
229
231
|
```js
|
package/docs/I18N.md
CHANGED
|
@@ -6,10 +6,12 @@ Every rule's title/description, and every occurrence's summary/hint, is localize
|
|
|
6
6
|
|
|
7
7
|
| Locale | File | Keys | Coverage vs. English |
|
|
8
8
|
|---|---|---|---|
|
|
9
|
-
| `en` (English) | `src/i18n/en.js` |
|
|
10
|
-
| `fr` (French) | `src/i18n/fr.js` |
|
|
9
|
+
| `en` (English) | `src/i18n/en.js` | 614 | 100% (the canonical/fallback set) |
|
|
10
|
+
| `fr` (French) | `src/i18n/fr.js` | 614 | 100% |
|
|
11
|
+
| `de` (German) | `src/i18n/de.js` | 614 | 100% |
|
|
12
|
+
| `es` (Spanish) | `src/i18n/es.js` | 614 | 100% |
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
All four locales are fully translated as of this writing. That won't stay automatically true — every time a new rule (or a new i18n key) is added to `en.js`, every other locale needs the same key added, or it silently falls back to English for that string (see the fallback behavior below). `tests/i18n/i18n-locale-completeness.test.js` catches this: it fails the build if a locale listed in `FULLY_TRANSLATED_LOCALES` (currently `fr`, `de`, `es`) is missing any key present in `en.js`, and fails for *any* locale that has an orphaned key not in `en.js` (a sign of a typo or a stale key left behind after a rule was removed). Run `npm run i18n:report` any time to see per-locale coverage.
|
|
13
15
|
|
|
14
16
|
## Selecting a locale
|
|
15
17
|
|
|
@@ -17,7 +19,7 @@ Both locales are fully translated as of this writing. That won't stay automatica
|
|
|
17
19
|
runDomRulesInPage(url, null, { locale: 'fr' }, null);
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
Default is `'en'` if omitted. Any string is accepted — an unrecognized locale (e.g. `'
|
|
22
|
+
Default is `'en'` if omitted. Any string is accepted — an unrecognized locale (e.g. `'ja'`, not yet built) behaves exactly like a locale that's 0% translated: every string falls through to English (see below), not an error.
|
|
21
23
|
|
|
22
24
|
## Fallback behavior (per-string, not per-locale)
|
|
23
25
|
|
|
@@ -40,8 +42,9 @@ Both are included in the result alongside the already-resolved text, so you can
|
|
|
40
42
|
|
|
41
43
|
## Contributing a translation
|
|
42
44
|
|
|
43
|
-
1.
|
|
44
|
-
2.
|
|
45
|
-
3.
|
|
46
|
-
4.
|
|
47
|
-
5.
|
|
45
|
+
1. Scaffold the file: `npm run i18n:new <locale>` (e.g. `npm run i18n:new de`) creates `src/i18n/<locale>.js` with all of `en.js`'s keys already present, each seeded with the English text as a placeholder. It refuses to overwrite an existing locale file unless you pass `--force`.
|
|
46
|
+
2. Replace the placeholder values with real translations, key by key. Leave any you're unsure about as-is for now — a value identical to English is treated as untranslated, not broken (see the fallback behavior above).
|
|
47
|
+
3. Check progress any time with `npm run i18n:report` — it prints, per locale, how many keys have been translated vs. still match the English placeholder, plus any missing or orphaned keys.
|
|
48
|
+
4. You don't need every key on day one — the fallback behavior above means a partial translation degrades gracefully to English per-string, exactly like `fr`/`de`/`es` did during their own early stages. Ship what you have. A partial locale is valid and won't fail `i18n-locale-completeness.test.js` unless you also add it to `FULLY_TRANSLATED_LOCALES` in that file — only do that once `npm run i18n:report` shows 100% coverage.
|
|
49
|
+
5. Keep `{{placeholder}}` tokens (and `{{#foo}}...{{/foo}}` conditional blocks) in translated strings exactly as they appear in the English source — they're substituted/evaluated verbatim regardless of locale (e.g. `{{element}}`, `{{role}}`). Never translate HTML tag/attribute names (`<img>`, `aria-label`, `role="dialog"`, etc.) — they're code identifiers, not prose.
|
|
50
|
+
6. Run `npm run build && npm test` to confirm nothing broke, including locale-completeness.
|
package/docs/INTEGRATION.md
CHANGED
|
@@ -91,6 +91,24 @@ const result = await page.evaluate(wrapperFn, {
|
|
|
91
91
|
|
|
92
92
|
This is the only pattern that gives every rule real computed layout, so it's the one to reach for if you need `target-size-minimum` or any other geometry-dependent check to actually run instead of reporting `notApplicable`.
|
|
93
93
|
|
|
94
|
+
## Pattern 3 — a standalone `<script>` tag (no driver, no bundler)
|
|
95
|
+
|
|
96
|
+
Good for: a manual check against a page open in a real browser, a bookmarklet, or any other context where there's no automation driver and no build step to reach for.
|
|
97
|
+
|
|
98
|
+
`@surea11y/core` ships `surea11y.browser.js` at the package root, a bundle generated from the same rule sources as `src/core.js`. Loading it directly defines one global, `a11ycore`:
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<script src="node_modules/@surea11y/core/surea11y.browser.js"></script>
|
|
102
|
+
<script>
|
|
103
|
+
const result = a11ycore.runa11yCoreInPage(location.href, null, {}, null);
|
|
104
|
+
console.log(result.checksResults.filter((r) => r.outcome === 'fail'));
|
|
105
|
+
</script>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`a11ycore.runa11yCoreInPage` is the exact same function described in Pattern 2 above — the bundle exists only to solve *loading* it without `require`/a module system, not to add a separate API surface. It carries the same self-containment property (own inlined rule catalog, no free variables), which is what makes a plain `<script>` tag sufficient.
|
|
109
|
+
|
|
110
|
+
Deliberately excluded from this bundle: `runa11yCoreAcrossFrames`/`a11yCoreEnableFrameResponder`. Cross-frame scanning needs the embedded frame to load the engine and opt in too (see "Cross-frame scanning" below) — not a fit for a single dropped-in script tag. Use the npm package directly if you need it.
|
|
111
|
+
|
|
94
112
|
## Scoping a scan to part of the page
|
|
95
113
|
|
|
96
114
|
Pass a CSS selector as the 2nd argument (`contextSelector`) to scan one subtree instead of the whole document — e.g. `runDomRulesInPage(url, '#app', {}, null)` to skip a surrounding CMS chrome you don't control. Pass an array of selectors (or a single comma-separated selector string) to scan multiple, possibly disjoint regions in one run — e.g. `runDomRulesInPage(url, ['#header', '#main'], {}, null)`. See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md) for the full `contextSelector` reference and for `excludeSelectors`, the complementary "skip specific elements anywhere" option.
|
|
@@ -112,7 +130,7 @@ if (failures.length > 0) {
|
|
|
112
130
|
|
|
113
131
|
Notes for CI specifically:
|
|
114
132
|
- `cantTell` outcomes are advisory by design (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md#outcome-values)) — most teams log them without failing the build, since they require human judgment the CI run can't make.
|
|
115
|
-
- For "only fail on *new* violations," the CLI has a built-in baseline/allowlist mechanism (`--write-baseline`/`--baseline`, see [`BASELINE.md`](./BASELINE.md)). Calling the library directly, the same matching logic is available as `buildBaselineEntries(result)`/`matchBaseline(result, baselineEntries)` from `require('@surea11y/core/
|
|
133
|
+
- For "only fail on *new* violations," the CLI has a built-in baseline/allowlist mechanism (`--write-baseline`/`--baseline`, see [`BASELINE.md`](./BASELINE.md)). Calling the library directly, the same matching logic is available as `buildBaselineEntries(result)`/`matchBaseline(result, baselineEntries)` from `require('@surea11y/core/baseline')` — or diff `checksResults` against a saved prior run yourself if your pages don't fit that model (see `BASELINE.md`'s "known limitation").
|
|
116
134
|
- Prefer Pattern 1 (jsdom) in CI unless you specifically need real-browser layout — it avoids the extra weight of a Puppeteer/Playwright + browser-binary install in your pipeline.
|
|
117
135
|
|
|
118
136
|
## Browser extension context
|