@surea11y/core 1.1.2 → 1.3.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 +48 -4
- package/LICENSE +373 -21
- package/README.md +70 -1
- package/bin/core.js +240 -11
- package/docs/API_STABILITY.md +61 -0
- package/docs/BASELINE.md +66 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +80 -1
- package/docs/ENGINE_OPTIONS.md +4 -0
- package/docs/INTEGRATION.md +19 -1
- package/docs/OUTPUT_SCHEMA.md +2 -2
- package/docs/REPORT.md +33 -0
- package/docs/RULE_AUTHORING.md +31 -0
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +50 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +125 -29
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +50 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +39 -9
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +117 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
- package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +26 -7
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -12
- package/src/checks/manual/empty-table-header-manual.js +21 -14
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -12
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
- package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +35 -21
- package/src/checks/manual/landmark-unique-manual.js +73 -45
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +25 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +40 -23
- package/src/checks/manual/page-title-patterns-manual.js +87 -51
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +275 -62
- package/src/checks/manual/scope-attr-valid-manual.js +11 -9
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +11 -9
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1128 -823
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4176 -3886
- package/src/core/dom-runner.js +720 -596
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +71 -35
- package/src/core.js +37939 -29121
- package/src/i18n/en.js +1194 -887
- package/src/i18n/fr.js +1136 -793
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +502 -0
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# CI/CD pipeline integrations
|
|
2
|
+
|
|
3
|
+
Ready-to-paste templates wrapping the [CLI](./CLI.md) (`npx @surea11y/core 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/core 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/core 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/core 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/core 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/core 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 [`CLI.md`](./CLI.md#what-it-can-and-cant-scan) for what that trades away (no client-rendered content, no real CSS layout).
|
package/docs/CLI.md
CHANGED
|
@@ -21,6 +21,11 @@ The CLI reads **static HTML only** — a local file, or the raw response of an H
|
|
|
21
21
|
| `--exclude-rules <ids>` | Comma-separated rule IDs — never run these. |
|
|
22
22
|
| `--tags <tags>` | Comma-separated tags — e.g. `--tags wcag2a,wcag2aa` to target a conformance level (see [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md)). |
|
|
23
23
|
| `--context <selector>` | Scope the scan to one CSS-selected subtree. |
|
|
24
|
+
| `--custom-rules <path>` | Load runtime custom rules from a local JS file. Repeatable. See [Custom rules](#custom-rules) below. |
|
|
25
|
+
| `--write-baseline <path>` | Write every current `fail` occurrence to `<path>`; never fails the build. See [`BASELINE.md`](./BASELINE.md). |
|
|
26
|
+
| `--baseline <path>` | Gate only on occurrences not already recorded in `<path>`. See [`BASELINE.md`](./BASELINE.md). |
|
|
27
|
+
| `--html <path>` | Write a self-contained, browsable HTML report to `<path>`. See [`REPORT.md`](./REPORT.md). |
|
|
28
|
+
| `--sarif <path>` | Write a SARIF 2.1.0 report to `<path>` (e.g. for GitHub Code Scanning). See [`SARIF.md`](./SARIF.md). |
|
|
24
29
|
| `-h`, `--help` | Show usage. |
|
|
25
30
|
| `-v`, `--version` | Show the installed version. |
|
|
26
31
|
|
|
@@ -36,13 +41,87 @@ These map directly onto `engineOptions`/`runOnly` (see [`ENGINE_OPTIONS.md`](./E
|
|
|
36
41
|
|
|
37
42
|
`cantTell` outcomes never affect the exit code — they're printed as a "needs human review" summary, consistent with the manual/`cantTell` mental model in [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md#should-i-treat-canttell-as-a-failure). If you need `cantTell`-aware gating, use `--json` and inspect `checksResults` yourself, or call the library directly (see [`INTEGRATION.md`](./INTEGRATION.md#ci-gating-a-build-on-the-result)).
|
|
38
43
|
|
|
44
|
+
With `--baseline`, exit code `1` means at least one *new* (not-yet-baselined) `fail` occurrence, not any `fail` occurrence — see [`BASELINE.md`](./BASELINE.md).
|
|
45
|
+
|
|
46
|
+
## Baseline / allowlist
|
|
47
|
+
|
|
48
|
+
For an existing, imperfect site, gating on every `fail` on day one is often an adoption blocker. `--write-baseline`/`--baseline` let you accept the current state once and gate CI only on genuinely new violations from then on:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
surea11y scan ./dist/index.html --write-baseline baseline.json # once, commit the file
|
|
52
|
+
surea11y scan ./dist/index.html --baseline baseline.json # in CI, from then on
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See [`BASELINE.md`](./BASELINE.md) for the matching semantics, file format, and known limitations.
|
|
56
|
+
|
|
57
|
+
## Custom rules
|
|
58
|
+
|
|
59
|
+
For an org-specific check that isn't (and shouldn't be) one of the built-in rules — an internal design-system convention, a company style-guide requirement — `--custom-rules <path>` registers your own rule(s) for that one scan, on top of every built-in rule:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
surea11y scan ./dist/index.html --custom-rules ./a11y-rules.js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`a11y-rules.js` exports either a single rule descriptor or an array of them, using the same shape as a built-in rule module:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
// a11y-rules.js
|
|
69
|
+
module.exports = [
|
|
70
|
+
{
|
|
71
|
+
id: 'org-no-inline-onclick',
|
|
72
|
+
meta: { title: 'No inline onclick handlers', defaultSeverity: 'moderate' },
|
|
73
|
+
runInPage(ctx) {
|
|
74
|
+
const els = ctx.helpers.queryAll('[onclick]');
|
|
75
|
+
const occurrences = els.map((el) => ({
|
|
76
|
+
selector: ctx.helpers.buildSelector(el),
|
|
77
|
+
html: el.outerHTML,
|
|
78
|
+
summary: 'Inline onclick handler found.',
|
|
79
|
+
hint: 'Move event handling into an external script.'
|
|
80
|
+
}));
|
|
81
|
+
return {
|
|
82
|
+
ruleId: ctx.rule.ruleId,
|
|
83
|
+
outcome: occurrences.length ? 'fail' : 'pass',
|
|
84
|
+
occurrences
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `<path>` is a **local file**, `require()`d directly by the CLI — never a URL. (Unlike the scan target, which does accept a URL: fetching and executing remote code as a rule would be a very different, much riskier trust model than running a file you already have on disk.)
|
|
92
|
+
- Because the CLI runs your rule in the same Node process as the scan, `runInPage`/`applicability` can be plain functions — no `fn.toString()` string-source workaround needed (that's only required for callers, like a browser-automation binding, whose `engineOptions` crosses a serialization boundary). See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md) for the full descriptor contract (`meta` defaulting, the `ctx` shape, etc.) — it's identical here.
|
|
93
|
+
- Repeat the flag to load rules from more than one file: `--custom-rules ./a.js --custom-rules ./b.js`.
|
|
94
|
+
- A custom rule's `id` colliding with a built-in one **overrides** that built-in for the scan, surfaced via a `console.warn` and the result's top-level `overriddenBuiltinIds` array — see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md).
|
|
95
|
+
- The file itself is validated at load time (must export a descriptor, or array of descriptors, each with a string `id` and a function-or-source-string `runInPage`) — a malformed export exits `2` with a clear error rather than silently scanning with one fewer rule than expected.
|
|
96
|
+
- Works alongside every other flag, including `--rules`/`--exclude-rules`/`--tags` (which can target your custom rule's `id` exactly like a built-in one) and `--baseline`/`--html`/`--sarif`.
|
|
97
|
+
|
|
98
|
+
## HTML report
|
|
99
|
+
|
|
100
|
+
For a browsable view of a scan's results — hero summary, WCAG rollup grouped by conformance level, and a searchable/filterable occurrence table — rather than raw JSON or a terminal summary:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
surea11y scan ./dist/index.html --html report.html
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Open `report.html` directly from disk; no server, no external assets. Works alongside any other output mode. See [`REPORT.md`](./REPORT.md).
|
|
107
|
+
|
|
108
|
+
## SARIF report
|
|
109
|
+
|
|
110
|
+
For GitHub Code Scanning or another SARIF-consuming dashboard:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
surea11y scan ./dist/index.html --sarif results.sarif
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Works alongside any other output mode, and alongside `--baseline` (already-known `fail` occurrences are omitted from the SARIF output rather than re-reported). See [`SARIF.md`](./SARIF.md).
|
|
117
|
+
|
|
39
118
|
## In CI
|
|
40
119
|
|
|
41
120
|
```sh
|
|
42
121
|
npx @surea11y/core scan ./dist/index.html || exit 1
|
|
43
122
|
```
|
|
44
123
|
|
|
45
|
-
Or, since the exit code already reflects pass/fail, just let the command's own exit code propagate — most CI systems fail the step automatically on a non-zero exit.
|
|
124
|
+
Or, since the exit code already reflects pass/fail, just let the command's own exit code propagate — most CI systems fail the step automatically on a non-zero exit. See [`CI_INTEGRATIONS.md`](./CI_INTEGRATIONS.md) for ready-to-paste GitHub Actions and Bitbucket Pipelines templates, including a SARIF-upload example.
|
|
46
125
|
|
|
47
126
|
## A note on dependencies
|
|
48
127
|
|
package/docs/ENGINE_OPTIONS.md
CHANGED
|
@@ -74,6 +74,7 @@ const engineOptions = {
|
|
|
74
74
|
locale: 'en', // default 'en'; falls back to 'en' per-string if a key is missing in the requested locale
|
|
75
75
|
includeHiddenElements: false, // default false — set true to evaluate hidden/collapsed subtrees too
|
|
76
76
|
includeShadowDom: true, // default true — opt OUT with `false` to skip open shadow roots
|
|
77
|
+
fragment: false, // default false — set true when the scan target isn't a real page (see below)
|
|
77
78
|
excludeSelectors: ['#cookie-banner', '.third-party-widget'], // array or comma-separated string
|
|
78
79
|
timestamp: '2026-07-20T12:00:00Z', // optional — engine has no built-in clock, see OUTPUT_SCHEMA.md
|
|
79
80
|
perfStats: false, // default false — internal timing counters, debug-only shape
|
|
@@ -117,6 +118,7 @@ const engineOptions = {
|
|
|
117
118
|
| `locale` | Any string; resolution is per-string with graceful fallback (requested locale → `en` → the rule's literal English fallback text), so a partially-translated locale never produces missing text. See [`I18N.md`](./I18N.md) for current locale coverage. |
|
|
118
119
|
| `includeHiddenElements` | Default `false`: helper queries exclude elements hidden by structural/CSS mechanisms such as `display:none`, `[hidden]`, closed `<details>`, and hidden rendering-only host elements (with descendants excluded too). Set `true` to include those hidden/collapsed subtrees in evaluation (legacy/static-markup behavior). |
|
|
119
120
|
| `includeShadowDom` | Default `true`: rules using `helpers.queryAllSmart` traverse into open shadow roots. Set `false` to scan only the light DOM. Closed shadow roots are never reachable either way (no DOM API exposes them). |
|
|
121
|
+
| `fragment` | Default `false`. A handful of rules check for the presence of a property that exists once per real page — `page-title-present`, `html-lang-attr-present`, `html-xml-lang-mismatch`, `aria-hidden-body`, `css-orientation-lock`, `meta-refresh-no-exceptions`, `meta-refresh-timing-absent`, `meta-viewport-zoom-enabled`, `meta-viewport-large`, `page-title-patterns`, `region`, `bypass-blocks-present`, `landmark-one-main`, `page-has-heading-one` — and correctly report `notApplicable` for these once `contextSelector` has scoped a run narrower than the whole document (`document.documentElement` no longer among the resolved roots), since a scoped subtree was never expected to carry its own `<title>`/`<html lang>`/etc. Set `fragment: true` for the case that scoping alone can't detect: a scan target that's the *whole* given document but was never meant to represent a real page at all (e.g. a raw component snippet parsed on its own) — this forces the same `notApplicable` gating even when unscoped. See `RULE_AUTHORING.md` §11.2 ("Whole-document checks") for the underlying rule-authoring convention, and `helpers.isWholeDocumentScope()` (`src/core/dom-helpers.js`) for the mechanism these 14 rules gate on via their `applicability(ctx)` export. |
|
|
120
122
|
| `excludeSelectors` | Elements matching any of these selectors (and their descendants) are skipped entirely, for **every** rule — useful for cookie banners, third-party embeds, or known-noisy widgets you don't control. To exclude something from just one specific rule instead, use `rules[ruleId].excludeSelectors` below. |
|
|
121
123
|
| `timestamp` | Passed straight through to the result's top-level `timestamp` field; the engine does not generate one itself (deterministic-by-design). |
|
|
122
124
|
| `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. |
|
|
@@ -222,6 +224,8 @@ See the option-by-option table above for anything not shown here, and the `custo
|
|
|
222
224
|
|
|
223
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.
|
|
224
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 [`CLI.md`](./CLI.md#custom-rules).
|
|
228
|
+
|
|
225
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:
|
|
226
230
|
|
|
227
231
|
```js
|
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
|
-
-
|
|
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/src/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
|
package/docs/OUTPUT_SCHEMA.md
CHANGED
|
@@ -30,7 +30,7 @@ This is the exact shape of the object returned by `runDomRulesInPage(...)` / `ru
|
|
|
30
30
|
| Field | Meaning |
|
|
31
31
|
|---|---|
|
|
32
32
|
| `engine.tag` | The engine's own identity tag, currently `"a11ycore"`. Every rule (built-in or custom) carries it in `meta.tags` — rule `ruleId`s themselves are bare (no prefix). |
|
|
33
|
-
| `engine.schemaVersion` | The result-schema version (`"1.0.0"`). Bump-worthy if this document's shape ever changes incompatibly — pin to it if you're parsing output programmatically. |
|
|
33
|
+
| `engine.schemaVersion` | The result-schema version (`"1.0.0"`). Bump-worthy if this document's shape ever changes incompatibly — pin to it if you're parsing output programmatically. See [`API_STABILITY.md`](./API_STABILITY.md) for the full stable/unstable field list and version-bump policy. |
|
|
34
34
|
| `url` | The `pageUrl` argument you passed in, or `document.location.href` if you passed `null`/omitted it, or `null` if neither is available. |
|
|
35
35
|
| `title` | `document.title` at scan time, or `null`. |
|
|
36
36
|
| `timestamp` | **Not auto-generated.** Only set if you pass `engineOptions.timestamp` as a non-empty string — the engine has no built-in clock (deterministic-by-design). If you want a scan timestamp in the result, supply it yourself. |
|
|
@@ -56,7 +56,7 @@ This is the exact shape of the object returned by `runDomRulesInPage(...)` / `ru
|
|
|
56
56
|
|
|
57
57
|
- `topFrame` is exactly the [top-level result](#top-level-result) shape, for the frame the function was called in.
|
|
58
58
|
- `frames` has one entry per direct child `<iframe>`/`<frame>` in the scanned scope. A reachable child (one that called `a11yCoreEnableFrameResponder()`) contributes its own complete `{ url, topFrame, frames }` — including *its own* nested `frames`, recursively, since a further-nested grandchild is only reachable through its immediate parent. An unreachable child (the common case for most third-party embeds — no cooperating responder, or it timed out) contributes `{ url, error }` instead, and does not abort the rest of the scan.
|
|
59
|
-
- This is a **tree, not a flat list** — a deliberate difference from the
|
|
59
|
+
- This is a **tree, not a flat list** — a deliberate difference from the `@surea11y/playwright` binding's `.frames(true)`, which *can* flatten because Playwright's `page.frames()` already gives every frame regardless of nesting depth; a `postMessage` relay has no such global view, so nesting is expressed structurally instead.
|
|
60
60
|
|
|
61
61
|
## A check result (`checksResults[i]`)
|
|
62
62
|
|
package/docs/REPORT.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# HTML report
|
|
2
|
+
|
|
3
|
+
A self-contained HTML report you can open in a browser after a scan — no server, no external assets, no network requests. Built for QA/manual testers who want a browsable view of a scan's results rather than raw JSON or a terminal summary.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
surea11y scan ./dist/index.html --html report.html
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Open `report.html` directly from disk. Works alongside any other output mode — `--html` doesn't replace `--json`/the default summary, it's an additional artifact written to the given path.
|
|
10
|
+
|
|
11
|
+
## What it shows
|
|
12
|
+
|
|
13
|
+
- **Hero**: one plain-language headline ("N of M applicable checks passed") plus a stacked bar and legend (icon + label + count — status is never color-only) broken down by outcome (`fail`/`cantTell`/`pass`/`notApplicable`).
|
|
14
|
+
- **Worth reviewing**: one card per rule with `fail`/`cantTell` occurrences (not one per occurrence — a rule with many identical occurrences is one thing worth attention, not many), each showing severity, WCAG SC chip(s), a representative occurrence, and the total occurrence count. Capped at the 24 highest-priority rules with an overflow note past that.
|
|
15
|
+
- **WCAG rollup**: grouped by conformance level (A / AA / AAA), sourced directly from the engine's own `rulesResults[]` composite rollups (`docs/WCAG_CONFORMANCE.md`) — one row per Success Criterion, its outcome, a pass/fail/needs-review/n/a breakdown, and which atomic rules contributed. This is real engine data, not an invented grouping — the same rollup you'd get from the raw JSON's `rulesResults`.
|
|
16
|
+
- **Full technical data** (collapsed by default): a scorecard (tiles per outcome) and a searchable, filterable (by outcome), paginated table of every individual occurrence across the whole scan.
|
|
17
|
+
|
|
18
|
+
## Library usage
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
const { renderHtmlReport } = require('@surea11y/core/src/report');
|
|
22
|
+
const { runDomRulesInPage } = require('@surea11y/core');
|
|
23
|
+
|
|
24
|
+
const result = runDomRulesInPage(url, null, {}, null);
|
|
25
|
+
const html = renderHtmlReport(result, { title: 'My scan report' });
|
|
26
|
+
require('fs').writeFileSync('report.html', html);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`renderHtmlReport(result, options)` is a pure function — it returns a string, it never touches the filesystem itself (the CLI's `--html` flag does the writing). `options.title` is optional (defaults to `"surea11y scan report"`).
|
|
30
|
+
|
|
31
|
+
## Scope
|
|
32
|
+
|
|
33
|
+
This is a single-scan report — one point-in-time snapshot, not a dashboard tracking results across many scans over time. Multi-run history/trend tracking is a separate, larger concern (see the project roadmap's "Enterprise/compliance features" — historical trend tracking across scans) and isn't part of this tool.
|
package/docs/RULE_AUTHORING.md
CHANGED
|
@@ -144,6 +144,23 @@ Each atomic rule declares which “facet(s)” of an SC it covers.
|
|
|
144
144
|
|
|
145
145
|
Keep facet naming consistent across a family.
|
|
146
146
|
|
|
147
|
+
#### `meta.deprecated` / `meta.deprecation`
|
|
148
|
+
Optional — how to retire a rule ID without breaking downstream consumers. See [`API_STABILITY.md`](./API_STABILITY.md) for the full policy (a deprecated rule keeps running normally; this is a catalog-level migration signal, not an automatic exclusion). Shape:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const meta = {
|
|
152
|
+
// ...
|
|
153
|
+
deprecated: true,
|
|
154
|
+
deprecation: {
|
|
155
|
+
replacedBy: 'new-rule-id', // or null
|
|
156
|
+
reason: 'Why this rule is being retired.',
|
|
157
|
+
sinceVersion: '1.2.0'
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`deprecated: true` without both `deprecation.reason` and `.sinceVersion` throws at build time (`normalizeRuleMeta`, `src/core/rule-meta.js`).
|
|
163
|
+
|
|
147
164
|
---
|
|
148
165
|
|
|
149
166
|
## 5) i18n in occurrences (repo reality)
|
|
@@ -325,6 +342,20 @@ why:
|
|
|
325
342
|
outcome is demonstrable per fixture file. Pick the most illustrative FAIL case; note
|
|
326
343
|
that PASS/other branches are covered by the rule's inline unit tests instead of
|
|
327
344
|
minting near-duplicate fixture files.
|
|
345
|
+
|
|
346
|
+
This category isn't just a fixture-authoring convention — it now backs a real
|
|
347
|
+
behavioral contract. These 14 rules (`page-title-present`, `html-lang-attr-present`,
|
|
348
|
+
`html-xml-lang-mismatch`, `aria-hidden-body`, `css-orientation-lock`,
|
|
349
|
+
`meta-refresh-no-exceptions`, `meta-refresh-timing-absent`, `meta-viewport-zoom-enabled`,
|
|
350
|
+
`meta-viewport-large`, `page-title-patterns`, `region`, `bypass-blocks-present`,
|
|
351
|
+
`landmark-one-main`, `page-has-heading-one`) each export an `applicability(ctx)`
|
|
352
|
+
gating on `helpers.isWholeDocumentScope()` (`src/core/dom-helpers.js`) — `notApplicable`
|
|
353
|
+
when `contextSelector` scoped the run narrower than the whole document, or when
|
|
354
|
+
`engineOptions.fragment: true` was set (see `ENGINE_OPTIONS.md`). A scoped subtree
|
|
355
|
+
or a bare component fragment was never expected to carry its own `<title>`/`<html lang>`/
|
|
356
|
+
page-wide landmark structure, so flagging its absence there is a false positive, not a
|
|
357
|
+
real finding. If you add a new rule to this category, add the same `applicability`
|
|
358
|
+
export rather than letting it silently evaluate document-wide facts regardless of scope.
|
|
328
359
|
- **Runtime-mutation-only branches** (e.g. `iframe-focusable-content`'s FAIL branch,
|
|
329
360
|
which requires mutating `iframe.contentDocument` after parse — jsdom does not
|
|
330
361
|
populate `srcdoc` synchronously): cover every branch that IS expressible statically;
|
package/docs/RULE_CATALOG.md
CHANGED
|
@@ -134,7 +134,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
134
134
|
| `region` | Page content should be inside a landmark region | — | — | medium | minor |
|
|
135
135
|
| `scope-attr-valid` | scope attribute must have a valid value | — | — | medium | minor |
|
|
136
136
|
| `scrollable-region-focusable` | Scrollable regions with no focusable content should be keyboard-focusable | 2.1.1, 2.1.3 | AAA | low | moderate |
|
|
137
|
-
| `skip-link` | Skip link must have a resolvable target | — | — | medium | minor |
|
|
137
|
+
| `skip-link` | Skip link must have a resolvable, usable target | — | — | medium | minor |
|
|
138
138
|
| `svg-text-alternative-quality` | <svg> text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
139
139
|
| `tabindex` | tabindex should not be greater than 0 | — | — | medium | minor |
|
|
140
140
|
| `table-duplicate-name` | Table caption must not duplicate its summary attribute | — | — | medium | minor |
|
package/docs/SARIF.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SARIF report
|
|
2
|
+
|
|
3
|
+
`--sarif <path>` writes a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) log — the standard format GitHub Code Scanning (and other SARIF-consuming dashboards) expect — instead of, or alongside, `--json`'s raw engine result.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
surea11y scan ./dist/index.html --sarif results.sarif
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
See [`CI_INTEGRATIONS.md`](./CI_INTEGRATIONS.md) for a ready-to-paste GitHub Actions workflow that runs a scan and uploads `results.sarif` to the "Security" tab.
|
|
10
|
+
|
|
11
|
+
## Why a separate format from `--json`
|
|
12
|
+
|
|
13
|
+
`--json`'s raw result (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md)) is this engine's own contract, versioned and stable per [`API_STABILITY.md`](./API_STABILITY.md). SARIF is a different, externally-defined contract purpose-built for code-scanning dashboards — a `checksResults[]` entry and a SARIF `result` don't map 1:1, so this is a real conversion, not a re-serialization.
|
|
14
|
+
|
|
15
|
+
## What becomes a SARIF result
|
|
16
|
+
|
|
17
|
+
Only `fail`/`cantTell` occurrences produce SARIF results — a `pass`/`notApplicable` check has no occurrences to report at all (same "violations only" framing as [`REPORT.md`](./REPORT.md)'s HTML report).
|
|
18
|
+
|
|
19
|
+
| Engine outcome | SARIF `level` | Meaning |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `fail` | `error` | Deterministic, high-confidence violation — the CI-gating case. |
|
|
22
|
+
| `cantTell` | `warning` | Needs human review — surfaced, but shouldn't block a build on its own. |
|
|
23
|
+
|
|
24
|
+
Every rule that ran (regardless of whether it produced a result) is listed once in `runs[0].tool.driver.rules`, with `defaultConfiguration.level` set from the rule's `type`: `automatic` (fail-capable) → `error`, `manual` (capped at `cantTell`) → `warning`.
|
|
25
|
+
|
|
26
|
+
## Field mapping
|
|
27
|
+
|
|
28
|
+
| SARIF field | Source |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `results[].ruleId` | `checksResults[i].ruleId` |
|
|
31
|
+
| `results[].message.text` | `occurrence.summary` + `occurrence.hint` |
|
|
32
|
+
| `results[].locations[].physicalLocation.artifactLocation.uri` | The scanned target — see "Locations" below. |
|
|
33
|
+
| `results[].locations[].logicalLocations[].fullyQualifiedName` | `occurrence.selector`, when present. |
|
|
34
|
+
| `results[].partialFingerprints["surea11y/violation/v1"]` | The same `ruleId + reasonCode + html` identity key used by [`BASELINE.md`](./BASELINE.md) (`computeBaselineKey`) — a stable, content-based fingerprint rather than a position-based one. |
|
|
35
|
+
| `results[].properties.severity` / `.confidence` | `checksResults[i].severity` / `.confidence` — informational, not part of SARIF's own schema. |
|
|
36
|
+
| `tool.driver.rules[].properties.tags` | `accessibility`, `automatic`/`manual`, and a `wcag-<SC>` tag per `meta.normativeMappings[].requirement`. |
|
|
37
|
+
|
|
38
|
+
## Locations
|
|
39
|
+
|
|
40
|
+
DOM-based scanning has no line/column to report, so `physicalLocation.artifactLocation.uri` is the scanned target itself, not a source-file position:
|
|
41
|
+
|
|
42
|
+
- **Local file scans**: a path relative to the current working directory (forward-slashed). If this matches a real file in your repository, GitHub Code Scanning can render the finding as an inline annotation.
|
|
43
|
+
- **URL scans**: the scanned URL itself. GitHub Code Scanning will still list the finding, but can't attach an inline annotation to a URL that isn't a file in the repository — this is inherent to how SARIF/Code Scanning associate findings with source, not a surea11y limitation. If you need inline annotations, scan the rendered HTML file (e.g. a build output artifact) rather than a live URL.
|
|
44
|
+
|
|
45
|
+
`occurrence.selector` is additionally carried as a `logicalLocations[].fullyQualifiedName`, so a consumer that reads logical locations still gets the "which element" signal even without a usable physical location.
|
|
46
|
+
|
|
47
|
+
## Combining with `--baseline`
|
|
48
|
+
|
|
49
|
+
A generic SARIF consumer has no "known, don't gate on this" concept of its own — the only faithful way to honor a baseline in SARIF output is to omit already-known `fail` occurrences entirely, rather than downgrade them to `warning`:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
surea11y scan ./dist/index.html --baseline baseline.json --sarif results.sarif
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`cantTell` occurrences are never filtered by a baseline — the baseline mechanism only ever tracks `fail` occurrences (matching `--write-baseline`, see [`BASELINE.md`](./BASELINE.md)).
|
|
56
|
+
|
|
57
|
+
## Combining with `--html`/`--json`
|
|
58
|
+
|
|
59
|
+
`--sarif`, `--html`, and `--json` are independent output flags — pass any combination in one run; each writes/prints its own report from the same single scan.
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surea11y/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Lightweight DOM rules accessibility core with modular rules.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"surea11y": "bin/core.js"
|
|
8
8
|
},
|
|
9
9
|
"author": "Jorge Rumoroso",
|
|
10
|
-
"license": "
|
|
10
|
+
"license": "MPL-2.0",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "git+https://github.com/rumoroso/surea11y-core.git"
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"src",
|
|
27
27
|
"bin",
|
|
28
|
+
"surea11y.browser.js",
|
|
28
29
|
"docs/**/*.md",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE",
|
|
@@ -36,9 +37,14 @@
|
|
|
36
37
|
"!src/explain"
|
|
37
38
|
],
|
|
38
39
|
"scripts": {
|
|
39
|
-
"
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"lint:fix": "eslint . --fix",
|
|
42
|
+
"format": "prettier --write \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.js\" \"tests/**/*.js\" \"eslint.config.js\"",
|
|
43
|
+
"format:check": "prettier --check \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.js\" \"tests/**/*.js\" \"eslint.config.js\"",
|
|
44
|
+
"build": "node scripts/build-core.js && node scripts/build-browser.js",
|
|
40
45
|
"pretest": "playwright install chromium",
|
|
41
46
|
"test": "npm run build && node scripts/run-tests.js",
|
|
47
|
+
"test:coverage": "npm run build && node scripts/run-tests.js --experimental-test-coverage --test-coverage-include=src/**/*.js --test-coverage-include=bin/**/*.js",
|
|
42
48
|
"test:contrast-helpers": "node tests/contrast-helpers.test.js",
|
|
43
49
|
"helpers-perf-bench": "node --expose-gc scripts/dom-helpers-perf-bench.js",
|
|
44
50
|
"engine-perf-bench": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --iters=25 --top=15",
|
|
@@ -56,6 +62,11 @@
|
|
|
56
62
|
"jsdom": "^29.1.1"
|
|
57
63
|
},
|
|
58
64
|
"devDependencies": {
|
|
59
|
-
"
|
|
65
|
+
"@eslint/js": "^10.0.1",
|
|
66
|
+
"eslint": "^10.8.0",
|
|
67
|
+
"eslint-config-prettier": "^10.1.8",
|
|
68
|
+
"globals": "^17.8.0",
|
|
69
|
+
"playwright": "^1.62.1",
|
|
70
|
+
"prettier": "^3.9.6"
|
|
60
71
|
}
|
|
61
72
|
}
|
package/src/baseline.js
ADDED
|
Binary file
|