a11ign 0.1.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/LICENSE +661 -0
- package/README.md +140 -0
- package/dist/action/post-comment.d.ts +20 -0
- package/dist/action/post-comment.d.ts.map +1 -0
- package/dist/action/post-comment.js +93 -0
- package/dist/action/post-comment.js.map +1 -0
- package/dist/action/run.d.ts +2 -0
- package/dist/action/run.d.ts.map +1 -0
- package/dist/action/run.js +119 -0
- package/dist/action/run.js.map +1 -0
- package/dist/action/summary.d.ts +180 -0
- package/dist/action/summary.d.ts.map +1 -0
- package/dist/action/summary.js +355 -0
- package/dist/action/summary.js.map +1 -0
- package/dist/cli.d.ts +279 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1028 -0
- package/dist/cli.js.map +1 -0
- package/dist/fault-remediation.d.ts +87 -0
- package/dist/fault-remediation.d.ts.map +1 -0
- package/dist/fault-remediation.js +156 -0
- package/dist/fault-remediation.js.map +1 -0
- package/dist/forms/config.d.ts +69 -0
- package/dist/forms/config.d.ts.map +1 -0
- package/dist/forms/config.js +185 -0
- package/dist/forms/config.js.map +1 -0
- package/dist/forms/coverage.d.ts +51 -0
- package/dist/forms/coverage.d.ts.map +1 -0
- package/dist/forms/coverage.js +80 -0
- package/dist/forms/coverage.js.map +1 -0
- package/dist/forms/draft.d.ts +62 -0
- package/dist/forms/draft.d.ts.map +1 -0
- package/dist/forms/draft.js +165 -0
- package/dist/forms/draft.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +48 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +322 -0
- package/dist/report.js.map +1 -0
- package/dist/scan/axe-results.d.ts +25 -0
- package/dist/scan/axe-results.d.ts.map +1 -0
- package/dist/scan/axe-results.js +111 -0
- package/dist/scan/axe-results.js.map +1 -0
- package/dist/scan/axe.d.ts +158 -0
- package/dist/scan/axe.d.ts.map +1 -0
- package/dist/scan/axe.js +203 -0
- package/dist/scan/axe.js.map +1 -0
- package/dist/scan/page-title.d.ts +3 -0
- package/dist/scan/page-title.d.ts.map +1 -0
- package/dist/scan/page-title.js +41 -0
- package/dist/scan/page-title.js.map +1 -0
- package/dist/scan/run-axe.d.ts +2 -0
- package/dist/scan/run-axe.d.ts.map +1 -0
- package/dist/scan/run-axe.js +39 -0
- package/dist/scan/run-axe.js.map +1 -0
- package/package.json +60 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { taskVerdictLabel, judgeBackend } from "@a11ign/judge";
|
|
2
|
+
import { layerOf, orderByLayer, LAYER_LABEL } from "@a11ign/judge/layers";
|
|
3
|
+
import { notAConformanceClaim } from "@a11ign/evidence/conformance";
|
|
4
|
+
import { outcomeTally } from "@a11ign/judge/outcomes";
|
|
5
|
+
import { documentsSpannedSentence, insideFrame } from "./action/summary.js";
|
|
6
|
+
/** How much offending markup to quote as evidence. Enough to recognise the element, not the page. */
|
|
7
|
+
const EVIDENCE_CHARS = 100;
|
|
8
|
+
/**
|
|
9
|
+
* The vocabulary this report uses, explained ONCE, in reading order, before any of it appears.
|
|
10
|
+
*
|
|
11
|
+
* #40's acceptance test is a stranger reading one report end to end and being able to say which findings
|
|
12
|
+
* are claims and which need a human, and what to do about each. Before this, that answer was assembled
|
|
13
|
+
* from three separate places — an ASSERTED/INDICATOR legend printed only when a non-conformance finding
|
|
14
|
+
* existed (so a report of ONLY rule-asserted findings never explained the tag at all), a `cantTell`/
|
|
15
|
+
* `untested` gloss inside `outcomesSection`, and nothing anywhere for `passed`/`inapplicable`. A reader
|
|
16
|
+
* who reached the findings first, with no legend above them (the ASSERTED-only case), had no way to know
|
|
17
|
+
* what the tag meant.
|
|
18
|
+
*
|
|
19
|
+
* ONE explanation, always printed, in the order the terms are used below it. This is also this repo's
|
|
20
|
+
* own rule about a fact stated twice applied to prose rather than to code: the two other explanations
|
|
21
|
+
* this replaced were correct on their own and would have drifted from this one the first time either was
|
|
22
|
+
* edited alone.
|
|
23
|
+
*/
|
|
24
|
+
function howToReadThisSection() {
|
|
25
|
+
return [
|
|
26
|
+
"-- How to read this report --",
|
|
27
|
+
"Findings below are tagged with what kind of claim they are:",
|
|
28
|
+
" ASSERTED a confirmed problem -- the evidence establishes it directly. Fix it.",
|
|
29
|
+
" INDICATOR a likely problem, but this check is looser than the criterion itself.",
|
|
30
|
+
" Have a person confirm it before treating it as a failure.",
|
|
31
|
+
"Per-criterion outcomes (further down) use a wider vocabulary than \"finding\":",
|
|
32
|
+
" passed checked, and this criterion is fine",
|
|
33
|
+
" asserted this FAILS the criterion -- the evidence establishes it directly (ACT: `failed`)",
|
|
34
|
+
" referred worth a person's eyes; the tool cannot decide this one on its own (ACT: `cantTell`).",
|
|
35
|
+
" This is normal, not a malfunction -- most of what a real page produces lands here.",
|
|
36
|
+
" inapplicable nothing of this kind is on the page to be right or wrong about",
|
|
37
|
+
" untested nothing here checks this criterion yet",
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The rule layer's section.
|
|
42
|
+
*
|
|
43
|
+
* "not run" and "0 violations" must never look alike: one means the visual criteria are unchecked,
|
|
44
|
+
* the other means they were checked and passed. Reporting silence as a clean bill of health is the
|
|
45
|
+
* single most misleading thing this tool could do.
|
|
46
|
+
*/
|
|
47
|
+
function axeSection(axe) {
|
|
48
|
+
// #1596: a finding inside an embedded frame is not silently the page's own. `insideFrame` is #1388's rule, imported so
|
|
49
|
+
// the job summary and this report cannot disagree about which findings it covers.
|
|
50
|
+
const framed = (axe ?? []).filter(insideFrame).length;
|
|
51
|
+
const lines = [
|
|
52
|
+
"-- Rule-based layer (axe-core): contrast, colour, ARIA, parsing --",
|
|
53
|
+
axe === null
|
|
54
|
+
? "not run. Visual criteria are unchecked, not clean."
|
|
55
|
+
: `${axe.length} violation(s)${framed > 0 ? `, ${framed} inside a frame` : ""}:`,
|
|
56
|
+
];
|
|
57
|
+
for (const finding of axe ?? []) {
|
|
58
|
+
const marker = insideFrame(finding) ? " (in a frame; origin not examined)" : "";
|
|
59
|
+
lines.push(` [${finding.impact}] ${finding.wcag.join(", ") || "(no SC)"} ${finding.rule}: ${finding.help}${marker}`);
|
|
60
|
+
if (finding.nodes[0])
|
|
61
|
+
lines.push(` evidence: ${finding.nodes[0].html.slice(0, EVIDENCE_CHARS)}`);
|
|
62
|
+
}
|
|
63
|
+
if (framed > 0)
|
|
64
|
+
lines.push(FRAME_CAVEAT);
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* #1596: #1388's caveat, in the words the job summary uses, as plain text for a terminal. Its second sentence and the
|
|
69
|
+
* marker are pinned equal to the summary's by `report.test.ts`, so the two outputs say one thing about a frame.
|
|
70
|
+
*/
|
|
71
|
+
const FRAME_CAVEAT = " A finding marked in a frame concerns content inside an embedded frame. This run did not examine "
|
|
72
|
+
+ "whose frame it is, so it may be third-party content (an embed, advert or widget) the page's author cannot control.";
|
|
73
|
+
/**
|
|
74
|
+
* The judge's findings, grouped by the Perceive -> Navigate -> Interact waterfall.
|
|
75
|
+
*
|
|
76
|
+
* Most fundamental first, because a page you cannot perceive is not worth reporting navigation
|
|
77
|
+
* problems on.
|
|
78
|
+
*/
|
|
79
|
+
/**
|
|
80
|
+
* Which assessor ran, from the judge's OWN resolver so the two cannot disagree.
|
|
81
|
+
*
|
|
82
|
+
* The comment here already claimed that and it was not true: this read the env var itself, defaulting
|
|
83
|
+
* "local" in a second place. Four copies of that expression existed and one had drifted to `??`.
|
|
84
|
+
*/
|
|
85
|
+
function judgeLabel() {
|
|
86
|
+
const backend = judgeBackend();
|
|
87
|
+
return backend === "local" ? "trained scorer" : `${backend} judge`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The five conformance requirements, each as "established / not established".
|
|
91
|
+
*
|
|
92
|
+
* Printed on EVERY report, including one with no findings — that is the case it exists for. Success
|
|
93
|
+
* criteria tell you what was found; these tell you what the run was capable of concluding, and WCAG
|
|
94
|
+
* §5.2 is explicit that a claim needs all five. Requirement 2 in particular is why a truncated sweep
|
|
95
|
+
* cannot be reported as a clean page.
|
|
96
|
+
*/
|
|
97
|
+
function conformanceSection(requirements) {
|
|
98
|
+
if (!requirements?.length) {
|
|
99
|
+
return ["-- WCAG conformance requirements --",
|
|
100
|
+
" NOT REPORTED for this run, so nothing here should be read as full-page or full-process coverage."];
|
|
101
|
+
}
|
|
102
|
+
const lines = ["-- WCAG conformance requirements (§5.2) — what this run can and cannot conclude --"];
|
|
103
|
+
for (const requirement of requirements) {
|
|
104
|
+
lines.push(` ${requirement.number}. ${requirement.name}`);
|
|
105
|
+
lines.push(` established: ${requirement.establishes}`);
|
|
106
|
+
lines.push(` limit: ${requirement.limitation}`);
|
|
107
|
+
}
|
|
108
|
+
// Last, and unconditional. A document listing WCAG criteria, evidence and a date looks exactly like a
|
|
109
|
+
// conformance claim to a reader who has not read §5.3 — and being mistaken for a certificate is the most
|
|
110
|
+
// damaging way this output could be misread.
|
|
111
|
+
const disclaimer = notAConformanceClaim();
|
|
112
|
+
lines.push(` ${disclaimer.name}`);
|
|
113
|
+
lines.push(` ${disclaimer.limitation}`);
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The tag a stranger meets on every per-criterion line below, in words rather than ACT's own vocabulary
|
|
118
|
+
* (#242, wording decided by `ceo`). `cantTell` is the correct value for a machine — ACT's own term, and
|
|
119
|
+
* what `--json` still emits unchanged — but a reader who has not read the ACT spec meets it as if it were
|
|
120
|
+
* a malfunction, when it is the tool working exactly as designed: most of what a real page produces lands
|
|
121
|
+
* here, not on `failed`. The two things a reader must be able to tell apart: `asserted` — this FAILS the
|
|
122
|
+
* criterion — and `referred` — worth a person's eyes, the tool cannot decide this one. The ACT term itself
|
|
123
|
+
* appears exactly once, in the legend's parenthetical, never repeated at each finding.
|
|
124
|
+
*/
|
|
125
|
+
const HUMAN_OUTCOME_TAG = {
|
|
126
|
+
failed: "ASSERTED", cantTell: "REFERRED",
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Per-criterion outcomes in the W3C ACT vocabulary.
|
|
130
|
+
*
|
|
131
|
+
* The reason this is worth printing next to the findings: a findings list answers "what is wrong", and
|
|
132
|
+
* says nothing about the difference between checked-and-fine, nothing-of-that-kind-here, could-not-
|
|
133
|
+
* determine, and never-evaluated. Four states, previously all rendered as the absence of a line.
|
|
134
|
+
*
|
|
135
|
+
* `passed` criteria are counted but not listed — the tally carries them, and listing 8 passes invites
|
|
136
|
+
* exactly the "so the page is fine" reading the rest of this section exists to prevent. Everything that
|
|
137
|
+
* is NOT a pass is named, because those are the ones a reader has to act on or account for.
|
|
138
|
+
*/
|
|
139
|
+
function outcomesSection(outcomes) {
|
|
140
|
+
if (!outcomes?.length) {
|
|
141
|
+
return ["-- Per-criterion outcomes (W3C ACT) --",
|
|
142
|
+
" NOT REPORTED for this run, so an absence of findings below distinguishes nothing."];
|
|
143
|
+
}
|
|
144
|
+
const tally = outcomeTally(outcomes);
|
|
145
|
+
const lines = [
|
|
146
|
+
// Vocabulary explained once, in `howToReadThisSection`, before this section is ever reached.
|
|
147
|
+
// The LABEL here is presentation only — `tally.failed`/`tally.cantTell` still read the machine fields
|
|
148
|
+
// ACT and `--json` need (#242's boundary); only the words printed next to the counts change.
|
|
149
|
+
"-- Per-criterion outcomes (W3C ACT vocabulary) --",
|
|
150
|
+
` asserted ${tally.failed} referred ${tally.cantTell} passed ${tally.passed} `
|
|
151
|
+
+ `inapplicable ${tally.inapplicable} untested ${tally.untested}`,
|
|
152
|
+
];
|
|
153
|
+
// The ASSESSOR is in the tag, not left to be read out of the prose. ADR 0021 turns on which layer is
|
|
154
|
+
// entitled to claim what, so a reader deciding how much weight to give a `failed` needs to know whether
|
|
155
|
+
// it came from a DOM rule or from driving a real screen reader — and a consumer parsing these lines
|
|
156
|
+
// should not have to regex a sentence to find out. Absent means the screen-reader layer, which is the
|
|
157
|
+
// default assessor and does not earn a tag on every line.
|
|
158
|
+
for (const outcome of outcomes.filter((o) => o.outcome === "failed" || o.outcome === "cantTell")) {
|
|
159
|
+
const by = outcome.assessor ? ` · ${outcome.assessor}` : "";
|
|
160
|
+
lines.push(` [${HUMAN_OUTCOME_TAG[outcome.outcome]}${by}] ${outcome.criterion} — ${outcome.reason}`);
|
|
161
|
+
}
|
|
162
|
+
return lines;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The one-line verdict, which must not appear to contradict the list underneath it.
|
|
166
|
+
*
|
|
167
|
+
* It printed `No blocking findings: yes` directly above three findings marked `[SERIOUS]`. Both were
|
|
168
|
+
* accurate — `taskCompletable` is `!findings.some(f => f.severity === "blocker")`, and `serious` is a rung
|
|
169
|
+
* below `blocker` — but nothing on the page said so, and a reader seeing "yes" over three SERIOUS lines has
|
|
170
|
+
* to decide which of them is lying. Observed on the first real page this was ever pointed at.
|
|
171
|
+
*
|
|
172
|
+
* So the line states the count it is actually about. A number cannot contradict a list of the same things
|
|
173
|
+
* the way a bare "yes" can.
|
|
174
|
+
*
|
|
175
|
+
* The LLM backends are untouched: there `taskCompletable` really does answer "could a screen-reader user
|
|
176
|
+
* complete the task?", which is a yes/no question and reads correctly as one.
|
|
177
|
+
*/
|
|
178
|
+
function verdictHeadline(verdict) {
|
|
179
|
+
const label = taskVerdictLabel();
|
|
180
|
+
// CONFIDENCE DESCRIBES THE FINDINGS, so with none there is nothing for it to describe.
|
|
181
|
+
//
|
|
182
|
+
// `local-judge` defines it as `Math.min(...findings.map(f => f.confidence))` — "a report is only as good
|
|
183
|
+
// as its shakiest claim" — and returns 1 when the list is empty. Printed, that became
|
|
184
|
+
// `Findings at BLOCKER severity: none (overall confidence 1)`, which reads as certainty about the
|
|
185
|
+
// ABSENCE. It is the same unearned reassurance as a bare "0 findings", in the line this function was
|
|
186
|
+
// rewritten to stop making. The ACT tally below says what was and was not determined; this line should
|
|
187
|
+
// not appear to answer it first.
|
|
188
|
+
//
|
|
189
|
+
// Abstention is unaffected: it returns 0 findings AND confidence 0, and its own sentence says nothing
|
|
190
|
+
// was scored.
|
|
191
|
+
const confidence = verdict.findings.length === 0 ? "" : ` (overall confidence ${verdict.confidence})`;
|
|
192
|
+
if (label.isTaskClaim) {
|
|
193
|
+
return `${label.question}: ${verdict.taskCompletable ? "yes" : "no"}${confidence}`;
|
|
194
|
+
}
|
|
195
|
+
const blockers = verdict.findings.filter((f) => f.severity === "blocker").length;
|
|
196
|
+
const rest = verdict.findings.length - blockers;
|
|
197
|
+
const others = rest ? `; ${rest} finding(s) below that severity` : "";
|
|
198
|
+
return `Findings at BLOCKER severity: ${blockers === 0 ? "none" : blockers}${others}${confidence}`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* How far this page sat from the evidence the scorer was validated on.
|
|
202
|
+
*
|
|
203
|
+
* Printed whether or not the scorer declined, because those are the two answers a support region exists to
|
|
204
|
+
* separate and only one of them used to be visible. A page scored at the very edge of the distribution and
|
|
205
|
+
* one comfortably inside it produced identical reports.
|
|
206
|
+
*
|
|
207
|
+
* Silent for the LLM backends, which have no support region, and it says so when an artifact ships no
|
|
208
|
+
* reference — unknown must not read as safe.
|
|
209
|
+
*/
|
|
210
|
+
function noveltyLine(verdict) {
|
|
211
|
+
const novelty = verdict.novelty;
|
|
212
|
+
if (!novelty)
|
|
213
|
+
return [];
|
|
214
|
+
if (novelty.inSupport === null || novelty.nearestTrainingCosine == null) {
|
|
215
|
+
return ["Support: NOT MEASURED — this scorer artifact ships no reference distribution, so nothing "
|
|
216
|
+
+ "checked whether the page resembles what it was validated on."];
|
|
217
|
+
}
|
|
218
|
+
const verdictWord = novelty.inSupport ? "within" : "OUTSIDE";
|
|
219
|
+
return [`Support: ${verdictWord} the scorer's validated range `
|
|
220
|
+
+ `(nearest training similarity ${novelty.nearestTrainingCosine}, floor ${novelty.floor ?? "?"}).`];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The scorer's own Python inference-runtime versions, so a disputed finding is traceable to what it was
|
|
224
|
+
* scored under as well as to the weights (publish blocker B2). Silent when absent — an LLM backend, or a
|
|
225
|
+
* local-scorer artifact that predates this field.
|
|
226
|
+
*/
|
|
227
|
+
function runtimeLine(verdict) {
|
|
228
|
+
const runtime = verdict.runtime;
|
|
229
|
+
if (!runtime)
|
|
230
|
+
return [];
|
|
231
|
+
const parts = Object.entries(runtime).map(([name, version]) => `${name} ${version ?? "absent"}`);
|
|
232
|
+
return [`Scorer runtime: ${parts.join(", ")}.`];
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The screen reader and client versions the CAPTURE actually ran under, read from `environment` — the
|
|
236
|
+
* running instance, never a pin. Publish blocker B4: the shipped scorer was trained on the fleet's NVDA,
|
|
237
|
+
* so a consumer needs to know which build produced the evidence in front of them, not which one a
|
|
238
|
+
* lockfile or an installer manifest names. Silent when `environment` (or the fields inside it) is
|
|
239
|
+
* missing — an older capture that predates this block, or a non-NVDA backend with nothing to report.
|
|
240
|
+
*/
|
|
241
|
+
function screenReaderRuntimeLine(environment) {
|
|
242
|
+
const screenReaderVersion = environment?.screenReaderVersion;
|
|
243
|
+
const guidepupVersion = environment?.guidepupVersion;
|
|
244
|
+
if (!screenReaderVersion && !guidepupVersion)
|
|
245
|
+
return [];
|
|
246
|
+
const parts = [
|
|
247
|
+
screenReaderVersion ? `${environment?.screenReader ?? "screen reader"} ${screenReaderVersion}` : null,
|
|
248
|
+
guidepupVersion ? `guidepup ${guidepupVersion}` : null,
|
|
249
|
+
].filter((part) => part !== null);
|
|
250
|
+
return [`Screen reader runtime: ${parts.join(", ")}.`];
|
|
251
|
+
}
|
|
252
|
+
function findingsSection(verdict, screenReader, announcements, environment) {
|
|
253
|
+
const lines = [
|
|
254
|
+
// Names what actually assessed the page rather than claiming "AI judge". The shipped default is
|
|
255
|
+
// this project's own trained scorer, not an LLM — and when that scorer abstains on a page unlike
|
|
256
|
+
// its training data, nothing judged anything at all. A report that overstates its own assessor is
|
|
257
|
+
// the same defect as reporting "not run" as a pass.
|
|
258
|
+
`-- Lived-experience layer (${screenReader} + ${judgeLabel()}): ${announcements} announcements --`,
|
|
259
|
+
// See taskVerdictLabel: with the default local scorer this is "no finding was a blocker", not a
|
|
260
|
+
// task verdict — that scorer never sees the task. Only the LLM backends actually answer it.
|
|
261
|
+
verdictHeadline(verdict),
|
|
262
|
+
verdict.summary,
|
|
263
|
+
...noveltyLine(verdict),
|
|
264
|
+
...screenReaderRuntimeLine(environment),
|
|
265
|
+
...runtimeLine(verdict),
|
|
266
|
+
`${verdict.findings.length} finding(s):`,
|
|
267
|
+
// ASSERTED/INDICATOR is explained once, in `howToReadThisSection`, before any finding appears --
|
|
268
|
+
// see that function's header for why repeating it here (conditionally, and in different words) was
|
|
269
|
+
// itself a defect: a report with no INDICATOR findings never explained the tag at all.
|
|
270
|
+
];
|
|
271
|
+
let currentLayer = "";
|
|
272
|
+
for (const finding of orderByLayer(verdict.findings)) {
|
|
273
|
+
const layer = layerOf(finding.wcag);
|
|
274
|
+
if (layer !== currentLayer) {
|
|
275
|
+
currentLayer = layer;
|
|
276
|
+
lines.push(` ${LAYER_LABEL[layer]}`);
|
|
277
|
+
}
|
|
278
|
+
// ASSERTED vs INDICATOR is the ACT requirement mapping, and it is the most consequential thing on the
|
|
279
|
+
// line: it tells the reader whether they may act on this as a failure or must confirm it by hand.
|
|
280
|
+
// Absent means secondary, so an unmapped finding never reads as an assertion.
|
|
281
|
+
const claim = finding.mapping === "conformance" ? "ASSERTED" : "INDICATOR";
|
|
282
|
+
lines.push(` [${finding.severity.toUpperCase()}] ${finding.wcag} (confidence ${finding.confidence}) ${claim}`);
|
|
283
|
+
lines.push(` ${finding.issue}`);
|
|
284
|
+
lines.push(` evidence: ${finding.evidence}`);
|
|
285
|
+
}
|
|
286
|
+
return lines;
|
|
287
|
+
}
|
|
288
|
+
/** Two lines -- a gap, then the note -- when the capture named more than one document; none otherwise. */
|
|
289
|
+
function documentsSpannedLead(conformance) {
|
|
290
|
+
const sentence = documentsSpannedSentence(conformance);
|
|
291
|
+
return sentence ? ["", `NOTE: this capture's evidence spans more than one document. ${sentence}`] : [];
|
|
292
|
+
}
|
|
293
|
+
/** The whole report, ready to print. */
|
|
294
|
+
export function reportLines({ url, task, screenReader, announcements, verdict, axe, conformance, outcomes, environment }) {
|
|
295
|
+
return [
|
|
296
|
+
"",
|
|
297
|
+
"a11ign report",
|
|
298
|
+
"===================",
|
|
299
|
+
`URL: ${url}`,
|
|
300
|
+
`Task: ${task}`,
|
|
301
|
+
// #1387: the same lead the Action's summary gives, from the same sentence. §5.2 below still carries it,
|
|
302
|
+
// but that is the last section a reader meets, and every finding in between may describe either page.
|
|
303
|
+
...documentsSpannedLead(conformance),
|
|
304
|
+
"",
|
|
305
|
+
...howToReadThisSection(),
|
|
306
|
+
"",
|
|
307
|
+
...axeSection(axe),
|
|
308
|
+
"",
|
|
309
|
+
...findingsSection(verdict, screenReader, announcements, environment),
|
|
310
|
+
"",
|
|
311
|
+
...outcomesSection(outcomes),
|
|
312
|
+
"",
|
|
313
|
+
...conformanceSection(conformance),
|
|
314
|
+
"",
|
|
315
|
+
// Kept in the output on purpose: a report that lists only what a screen reader can hear invites
|
|
316
|
+
// the reader to conclude the rest is fine.
|
|
317
|
+
"Note: visual issues (contrast, colour, target size) come from the rule-based layer;",
|
|
318
|
+
"a screen reader cannot perceive them. Some criteria still need human review.",
|
|
319
|
+
"",
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AxeFinding, type RuleLayerCoverage } from "./axe.js";
|
|
2
|
+
interface ImportedAxe {
|
|
3
|
+
findings: AxeFinding[];
|
|
4
|
+
/** The URL the imported results were produced against, when the file records one. */
|
|
5
|
+
scannedUrl: string;
|
|
6
|
+
/**
|
|
7
|
+
* What the imported run examined — and on this path it is usually LESS than on our own.
|
|
8
|
+
*
|
|
9
|
+
* Many axe reporters emit `violations` and nothing else, so for those files a criterion with no
|
|
10
|
+
* violation may have been checked and passed, or never checked at all, and the file cannot tell you
|
|
11
|
+
* which. `coverageOf` therefore records ONLY violated criteria unless the file actually carries the
|
|
12
|
+
* other buckets. The result is that `--axe-results` reports fewer criteria as examined than a native
|
|
13
|
+
* scan, which is correct: it is the difference between what we know and what we would like to claim.
|
|
14
|
+
*/
|
|
15
|
+
coverage: RuleLayerCoverage;
|
|
16
|
+
}
|
|
17
|
+
export declare function loadAxeResults(path: string): Promise<ImportedAxe>;
|
|
18
|
+
/**
|
|
19
|
+
* Warn when imported results were produced against a different URL. Not an error — a
|
|
20
|
+
* staging host or a trailing slash is a legitimate difference — but a stale file from
|
|
21
|
+
* another page is a quiet way to report the wrong findings with total confidence.
|
|
22
|
+
*/
|
|
23
|
+
export declare function warnOnUrlMismatch(scannedUrl: string, target: string): void;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=axe-results.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe-results.d.ts","sourceRoot":"","sources":["../../src/scan/axe-results.ts"],"names":[],"mappings":"AAqBA,OAAO,EAA4B,KAAK,UAAU,EAAqB,KAAK,iBAAiB,EAAE,MACxF,UAAU,CAAC;AAElB,UAAU,WAAW;IACnB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAiED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBvE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAM1E"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import axe-core results that someone else produced (`--axe-results <file>`).
|
|
3
|
+
*
|
|
4
|
+
* Most teams adopting this tool already run axe in their pipeline. Running our own second
|
|
5
|
+
* copy would give them duplicate findings from a differently-versioned engine in the same
|
|
6
|
+
* CI — worse than not having the layer at all. So the rule-based layer can be *fed* rather
|
|
7
|
+
* than executed: they keep their axe run, we consume its output, and the two-layer report
|
|
8
|
+
* still works with no Chromium download and no second scan.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately tolerant about shape, because "axe results" means several different files
|
|
11
|
+
* depending on which tool wrote them, and the differences are packaging rather than
|
|
12
|
+
* substance. Accepted:
|
|
13
|
+
*
|
|
14
|
+
* { violations: [...] } axe.run() / @axe-core/playwright / axe-core reporters
|
|
15
|
+
* [ { violations: [...] } ] axe CLI, which emits one entry per URL scanned
|
|
16
|
+
* [ ... ] a bare violations array
|
|
17
|
+
*
|
|
18
|
+
* Anything else is rejected loudly. Silently reading zero violations out of a file we did
|
|
19
|
+
* not understand would print "0 violations" and read as a clean bill of health.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile } from "node:fs/promises";
|
|
22
|
+
import { coverageFrom, toFindings } from "./axe.js";
|
|
23
|
+
/**
|
|
24
|
+
* The rule-layer coverage an imported file supports — never more.
|
|
25
|
+
*
|
|
26
|
+
* The tolerance this module is built on cuts the other way here. It accepts three packagings because they
|
|
27
|
+
* differ in packaging rather than substance; but a bare violations array genuinely CONTAINS less, and
|
|
28
|
+
* inferring "examined and clean" from a file that only lists failures would manufacture the exact
|
|
29
|
+
* reassurance the header refuses when it rejects a file it does not understand.
|
|
30
|
+
*/
|
|
31
|
+
function coverageOf(parsed, violations) {
|
|
32
|
+
const buckets = (Array.isArray(parsed) ? parsed[0] : parsed);
|
|
33
|
+
const bucket = (name) => {
|
|
34
|
+
const value = buckets && typeof buckets === "object" ? buckets[name] : undefined;
|
|
35
|
+
return Array.isArray(value) ? value : undefined;
|
|
36
|
+
};
|
|
37
|
+
return coverageFrom({
|
|
38
|
+
violations,
|
|
39
|
+
incomplete: bucket("incomplete"),
|
|
40
|
+
passes: bucket("passes"),
|
|
41
|
+
inapplicable: bucket("inapplicable"),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Does this element look like an axe violation at all?
|
|
46
|
+
*
|
|
47
|
+
* A bare array was accepted UNCONDITIONALLY, so `--axe-results` pointed at the wrong JSON produced
|
|
48
|
+
* fabricated findings rather than the loud rejection this module's header promises. Measured: a Lighthouse
|
|
49
|
+
* report became 1 finding, an array of URL strings became 2, an array of numbers became 3 — each rendered
|
|
50
|
+
* into the rule layer with empty `rule`, `impact` and `help`, beside real screen-reader findings.
|
|
51
|
+
*
|
|
52
|
+
* That is worse than the "0 violations from a file we did not understand" the header warns about, because
|
|
53
|
+
* a fabricated count reads as a real one.
|
|
54
|
+
*
|
|
55
|
+
* `id` and `help` are the two fields every axe violation carries and no reporter renames. Requiring EITHER
|
|
56
|
+
* keeps the deliberate tolerance about packaging — the whole point of this module — while refusing a file
|
|
57
|
+
* that is not axe output at all.
|
|
58
|
+
*/
|
|
59
|
+
const looksLikeViolation = (value) => typeof value === "object" && value !== null
|
|
60
|
+
&& (typeof value.id === "string"
|
|
61
|
+
|| typeof value.help === "string");
|
|
62
|
+
function violationsFrom(parsed) {
|
|
63
|
+
if (Array.isArray(parsed)) {
|
|
64
|
+
// `every` on an EMPTY array is true, which is correct here: `[]` is a legitimate clean axe run and
|
|
65
|
+
// must stay accepted as zero violations rather than falling through to the shape check below.
|
|
66
|
+
if (parsed.every((e) => e && typeof e === "object" && "violations" in e)) {
|
|
67
|
+
return parsed.flatMap((e) => e.violations ?? []);
|
|
68
|
+
}
|
|
69
|
+
return parsed.every(looksLikeViolation) ? parsed : null;
|
|
70
|
+
}
|
|
71
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.violations)) {
|
|
72
|
+
return parsed.violations;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function scannedUrlFrom(parsed) {
|
|
77
|
+
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
78
|
+
const url = first?.url;
|
|
79
|
+
return typeof url === "string" ? url : "";
|
|
80
|
+
}
|
|
81
|
+
export async function loadAxeResults(path) {
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw new Error(`could not read axe results from ${path}: ${e.message}`, { cause: e });
|
|
88
|
+
}
|
|
89
|
+
const violations = violationsFrom(parsed);
|
|
90
|
+
if (!violations) {
|
|
91
|
+
throw new Error(`${path} does not look like axe results: expected { violations: [...] }, an array of those, ` +
|
|
92
|
+
"or a bare violations array.");
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
findings: toFindings(violations),
|
|
96
|
+
scannedUrl: scannedUrlFrom(parsed),
|
|
97
|
+
coverage: coverageOf(parsed, violations),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Warn when imported results were produced against a different URL. Not an error — a
|
|
102
|
+
* staging host or a trailing slash is a legitimate difference — but a stale file from
|
|
103
|
+
* another page is a quiet way to report the wrong findings with total confidence.
|
|
104
|
+
*/
|
|
105
|
+
export function warnOnUrlMismatch(scannedUrl, target) {
|
|
106
|
+
if (!scannedUrl || scannedUrl === target)
|
|
107
|
+
return;
|
|
108
|
+
process.stderr.write(`WARNING: the imported axe results were produced against ${scannedUrl}, not ${target}. ` +
|
|
109
|
+
"The rule-based findings may describe a different page.\n");
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=axe-results.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe-results.js","sourceRoot":"","sources":["../../src/scan/axe-results.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,UAAU,EAA8D,MACxF,UAAU,CAAC;AAkBlB;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,MAAe,EAAE,UAAmC;IACtE,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAmC,CAAC;IAC/F,MAAM,MAAM,GAAG,CAAC,IAAY,EAA8B,EAAE;QAC1D,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjF,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAwB,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,CAAC,CAAC;IACF,OAAO,YAAY,CAAC;QAClB,UAAU;QACV,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC;QAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC,cAAc,CAAC;KACrC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAW,EAAE,CACrD,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;OACxC,CAAC,OAAQ,KAA0B,CAAC,EAAE,KAAK,QAAQ;WACjD,OAAQ,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AAE/D,SAAS,cAAc,CAAC,MAAe;IACrC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,mGAAmG;QACnG,8FAA8F;QAC9F,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC;YACzE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAAoC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAE,MAAyB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,CAAC;IACD,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAE,MAAmC,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3G,OAAQ,MAAyC,CAAC,UAAU,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,MAAe;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,GAAG,GAAI,KAAuC,EAAE,GAAG,CAAC;IAC1D,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,KAAM,CAAW,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,sFAAsF;YAC3F,6BAA6B,CAChC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC;QAChC,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC;QAClC,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC;KACzC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,MAAc;IAClE,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,MAAM;QAAE,OAAO;IACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2DAA2D,UAAU,SAAS,MAAM,IAAI;QACtF,0DAA0D,CAC7D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule-based layer (ADR 0002): run axe-core over a page and return its
|
|
3
|
+
* WCAG-tagged violations. This is the deterministic, mechanical/visual layer
|
|
4
|
+
* (contrast, colour, ARIA, parsing, names/roles) that a screen-reader
|
|
5
|
+
* read-through cannot perceive. It complements the lived-experience judge; it
|
|
6
|
+
* does not replace it.
|
|
7
|
+
*
|
|
8
|
+
* Scoped to WCAG A/AA to match @a11ign/evidence/wcag and the legal baseline.
|
|
9
|
+
*
|
|
10
|
+
* OPTIONAL. Playwright and @axe-core/playwright are optionalDependencies: the layer is
|
|
11
|
+
* ~100 lines and about a second of wall-clock, but it pulls half a gigabyte of Chromium,
|
|
12
|
+
* which is a poor trade for anyone who already runs axe in their own pipeline. So the
|
|
13
|
+
* imports are dynamic and their absence is a supported state, not a crash. The
|
|
14
|
+
* lived-experience layer — the part only this project does — never depends on them.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A/AA across WCAG 2.0/2.1/2.2 (axe tags conformance level + version).
|
|
18
|
+
*
|
|
19
|
+
* EXPORTED so a test can hold it against what axe-core actually offers. Verified 2026-08-29: axe's level
|
|
20
|
+
* tags are exactly `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa` and `wcag2aaa` — this list is
|
|
21
|
+
* every A/AA one, with AAA correctly out of scope, and there is no `wcag22a` (WCAG 2.2's two Level A
|
|
22
|
+
* additions, 3.2.6 and 3.3.7, have no axe rule at all).
|
|
23
|
+
*
|
|
24
|
+
* The reason it is pinned rather than left correct: a tag added by a future axe version would silently
|
|
25
|
+
* narrow the scan, and a scan that quietly checks less still reports "0 violations".
|
|
26
|
+
*/
|
|
27
|
+
export declare const WCAG_AA_TAGS: string[];
|
|
28
|
+
import type { RuleLayerCoverage, RuleLayerVerdict } from "@a11ign/judge/outcomes";
|
|
29
|
+
export type { RuleLayerCoverage, RuleLayerVerdict };
|
|
30
|
+
export interface AxeFinding {
|
|
31
|
+
source: "axe-core";
|
|
32
|
+
/** WCAG success criteria this violation maps to, e.g. ["1.4.3"]. */
|
|
33
|
+
wcag: string[];
|
|
34
|
+
rule: string;
|
|
35
|
+
impact: string;
|
|
36
|
+
help: string;
|
|
37
|
+
helpUrl: string;
|
|
38
|
+
/** The failing elements (HTML snippet + CSS selector path). */
|
|
39
|
+
nodes: {
|
|
40
|
+
html: string;
|
|
41
|
+
target: string[];
|
|
42
|
+
}[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fold axe's four result buckets into one verdict per criterion.
|
|
46
|
+
*
|
|
47
|
+
* A criterion usually has SEVERAL axe rules, so the buckets must be reduced with a precedence and it is
|
|
48
|
+
* the strict one: a violation anywhere beats review-needed, which beats clean. Two rules for a criterion
|
|
49
|
+
* where one passes and one needs review leaves the criterion needing review — claiming otherwise would let
|
|
50
|
+
* a passing fragment vouch for a fragment nobody checked.
|
|
51
|
+
*
|
|
52
|
+
* `inapplicable` counts as CLEAN rather than as ACT's `inapplicable`, and that is deliberate. axe means
|
|
53
|
+
* "this RULE found no elements to test"; the criterion may still have aspects no axe rule covers, so the
|
|
54
|
+
* page having no images tells you nothing about the rest of 1.1.1. Reporting the criterion inapplicable
|
|
55
|
+
* from a rule's inapplicability would be a claim about the criterion drawn from a claim about one rule.
|
|
56
|
+
*
|
|
57
|
+
* Each entry also names the axe rule ids that VIOLATED the criterion (#1606), so a reason can say which rule axe reported.
|
|
58
|
+
* A criterion that was not violated names none. An imported file carrying only `violations` records only violated
|
|
59
|
+
* criteria here, now with their ids, because `axe-results.ts` passes its buckets straight through.
|
|
60
|
+
*/
|
|
61
|
+
export declare function coverageFrom(buckets: {
|
|
62
|
+
violations?: readonly AxeViolation[];
|
|
63
|
+
incomplete?: readonly AxeViolation[];
|
|
64
|
+
passes?: readonly AxeViolation[];
|
|
65
|
+
inapplicable?: readonly AxeViolation[];
|
|
66
|
+
}): RuleLayerCoverage;
|
|
67
|
+
export interface AxeResult {
|
|
68
|
+
findings: AxeFinding[];
|
|
69
|
+
/**
|
|
70
|
+
* Which criteria this scan actually EXAMINED, and what it concluded — the thing that was thrown away.
|
|
71
|
+
*
|
|
72
|
+
* `analyze()` returns four buckets and this module kept one. Keeping only `violations` makes "no
|
|
73
|
+
* violation for 3.1.1" mean both "axe checked and the page has a valid lang" and "axe never ran that
|
|
74
|
+
* rule", which is the ambiguity this whole project refuses everywhere else. The report then said "No
|
|
75
|
+
* assessor in this tool covers this criterion" about criteria axe had just checked.
|
|
76
|
+
*/
|
|
77
|
+
coverage: RuleLayerCoverage;
|
|
78
|
+
/** The page's document.title — used to verify the screen-reader worker
|
|
79
|
+
* actually captured THIS page and not browser chrome (see cli.ts). */
|
|
80
|
+
title: string;
|
|
81
|
+
/** Which browser actually ran the scan — see `launchBrowser`. Evidence, not incidental. */
|
|
82
|
+
browserChannel: AxeBrowserChannel;
|
|
83
|
+
}
|
|
84
|
+
/** One violation as axe-core reports it, in the shape both our own run and an imported
|
|
85
|
+
* results file share. Loosely typed on purpose: an imported file comes from someone
|
|
86
|
+
* else's axe version and may carry more or fewer fields than ours. */
|
|
87
|
+
export interface AxeViolation {
|
|
88
|
+
id?: unknown;
|
|
89
|
+
tags?: unknown;
|
|
90
|
+
impact?: unknown;
|
|
91
|
+
help?: unknown;
|
|
92
|
+
helpUrl?: unknown;
|
|
93
|
+
nodes?: unknown;
|
|
94
|
+
}
|
|
95
|
+
/** Map axe's violations to our findings. Shared so an imported results file and our own
|
|
96
|
+
* run produce identical output — a finding must not look different depending on who ran
|
|
97
|
+
* the scan. */
|
|
98
|
+
export declare function toFindings(violations: readonly AxeViolation[]): AxeFinding[];
|
|
99
|
+
/** Thrown when the optional browser dependencies are not installed. */
|
|
100
|
+
export declare class AxeUnavailableError extends Error {
|
|
101
|
+
constructor(cause: unknown);
|
|
102
|
+
}
|
|
103
|
+
/** Which browser actually answered — evidence, the way `browserVersion` is on the capture side. */
|
|
104
|
+
export type AxeBrowserChannel = "chromium" | "msedge";
|
|
105
|
+
/** Thrown when NEITHER the bundled browser nor the system channel could be launched. */
|
|
106
|
+
export declare class AxeLaunchError extends Error {
|
|
107
|
+
constructor(bundledError: unknown, channelError: unknown);
|
|
108
|
+
}
|
|
109
|
+
/** The minimal shape `launchBrowser` needs, so a test can inject a fake one without real Playwright. */
|
|
110
|
+
export interface LaunchableChromium {
|
|
111
|
+
launch(options?: {
|
|
112
|
+
channel?: string;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
close(): Promise<void>;
|
|
115
|
+
newContext(): Promise<unknown>;
|
|
116
|
+
}>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Launch a browser for axe to drive — the bundled Chromium first, a system channel as the fallback.
|
|
120
|
+
*
|
|
121
|
+
* FOUND 2026-09-06: `chromium.launch()` with no options needs the bundled browser, and the Action
|
|
122
|
+
* deliberately skips downloading it (`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` — half a gigabyte the Windows
|
|
123
|
+
* runner does not need, since the capture already drives Edge). So on the Action the bundled browser is
|
|
124
|
+
* ABSENT BY DESIGN, `launch()` threw, the throw was caught in `cli.ts` and reported as `ruleBased: null`
|
|
125
|
+
* — while the progress line printed "rule-based axe-core + real screen reader" and the smoke test never
|
|
126
|
+
* read `ruleBased` at all. Every Action consumer got the rule layer silently skipped.
|
|
127
|
+
*
|
|
128
|
+
* A hard-coded `channel: "msedge"` is not the fix: the CLI runs on Macs and Linux too, and a developer
|
|
129
|
+
* with no Edge installed would lose the layer entirely. So: try the bundled browser first (what a local
|
|
130
|
+
* `npx playwright install chromium` gives you), and only on failure fall back to the system channel —
|
|
131
|
+
* which the Windows runner has, and which happens to be the SAME engine the capture itself drives, so
|
|
132
|
+
* the two layers observe one rendering engine rather than two. Which one actually answered is returned
|
|
133
|
+
* rather than assumed, because it is evidence: a finding depends on the renderer that produced it.
|
|
134
|
+
*/
|
|
135
|
+
export declare function launchBrowser(chromium: LaunchableChromium): Promise<{
|
|
136
|
+
browser: Awaited<ReturnType<LaunchableChromium["launch"]>>;
|
|
137
|
+
channel: AxeBrowserChannel;
|
|
138
|
+
}>;
|
|
139
|
+
/**
|
|
140
|
+
* True when the rule-based layer can run here.
|
|
141
|
+
*
|
|
142
|
+
* USED TO BE cheap and wrong: it resolved the modules and stopped, which proves an IMPORT, not a LAUNCH —
|
|
143
|
+
* exactly the gap that let the Action announce the layer and then silently produce nothing for it. This
|
|
144
|
+
* now launches for real (bundled Chromium, then the system channel) and closes immediately, so the
|
|
145
|
+
* answer means what its name says. The cost is the same one `scanWithAxe` already pays once per run.
|
|
146
|
+
*
|
|
147
|
+
* `deps.loadAxe` is the injection seam for a test: neither of `loadAxe`'s own two failure modes (modules
|
|
148
|
+
* missing, no browser launchable) can be produced from inside this repo's CI without either uninstalling
|
|
149
|
+
* a dependency or faking the launch — so a test supplies a fake `loadAxe` whose `chromium.launch` always
|
|
150
|
+
* throws, and asserts this still answers `false` rather than the `true` an import-only check would give.
|
|
151
|
+
*/
|
|
152
|
+
export declare function axeAvailable(deps?: {
|
|
153
|
+
loadAxe?: () => Promise<{
|
|
154
|
+
chromium: LaunchableChromium;
|
|
155
|
+
}>;
|
|
156
|
+
}): Promise<boolean>;
|
|
157
|
+
export declare function scanWithAxe(url: string): Promise<AxeResult>;
|
|
158
|
+
//# sourceMappingURL=axe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../../src/scan/axe.ts"],"names":[],"mappings":"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"}
|