@setsquare/review-sdk 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +3 -2
- package/README.md +287 -3
- package/dist/autoscan.d.ts +23 -1
- package/dist/autoscan.d.ts.map +1 -1
- package/dist/autoscan.js +220 -15
- package/dist/autoscan.js.map +1 -1
- package/dist/brand.d.ts +51 -2
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +560 -5
- package/dist/brand.js.map +1 -1
- package/dist/checks/context-change.d.ts +96 -0
- package/dist/checks/context-change.d.ts.map +1 -0
- package/dist/checks/context-change.js +655 -0
- package/dist/checks/context-change.js.map +1 -0
- package/dist/checks/focus.d.ts +154 -0
- package/dist/checks/focus.d.ts.map +1 -0
- package/dist/checks/focus.js +1123 -0
- package/dist/checks/focus.js.map +1 -0
- package/dist/checks/heuristics.d.ts +117 -0
- package/dist/checks/heuristics.d.ts.map +1 -0
- package/dist/checks/heuristics.js +961 -0
- package/dist/checks/heuristics.js.map +1 -0
- package/dist/checks/keyboard.d.ts +106 -0
- package/dist/checks/keyboard.d.ts.map +1 -0
- package/dist/checks/keyboard.js +452 -0
- package/dist/checks/keyboard.js.map +1 -0
- package/dist/checks/reflow.d.ts +75 -0
- package/dist/checks/reflow.d.ts.map +1 -0
- package/dist/checks/reflow.js +285 -0
- package/dist/checks/reflow.js.map +1 -0
- package/dist/checks/rule-pack.d.ts +1 -1
- package/dist/checks/rule-pack.d.ts.map +1 -1
- package/dist/checks/rule-pack.js +350 -45
- package/dist/checks/rule-pack.js.map +1 -1
- package/dist/checks/shortcuts.d.ts +71 -0
- package/dist/checks/shortcuts.d.ts.map +1 -0
- package/dist/checks/shortcuts.js +196 -0
- package/dist/checks/shortcuts.js.map +1 -0
- package/dist/checks/versions.d.ts.map +1 -1
- package/dist/checks/versions.js +33 -1
- package/dist/checks/versions.js.map +1 -1
- package/dist/contract.d.ts +32 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/covers.d.ts +56 -0
- package/dist/covers.d.ts.map +1 -0
- package/dist/covers.js +135 -0
- package/dist/covers.js.map +1 -0
- package/dist/dedupe.d.ts +23 -0
- package/dist/dedupe.d.ts.map +1 -1
- package/dist/dedupe.js +96 -2
- package/dist/dedupe.js.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +144 -0
- package/dist/document.js.map +1 -1
- package/dist/envelope.d.ts +8 -0
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +106 -16
- package/dist/envelope.js.map +1 -1
- package/dist/fixture.d.ts.map +1 -1
- package/dist/fixture.js +33 -4
- package/dist/fixture.js.map +1 -1
- package/dist/html-report.d.ts +24 -7
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +608 -136
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts.map +1 -1
- package/dist/insights.js +9 -12
- package/dist/insights.js.map +1 -1
- package/dist/links.d.ts +43 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +43 -0
- package/dist/links.js.map +1 -1
- package/dist/observe.d.ts +8 -0
- package/dist/observe.d.ts.map +1 -1
- package/dist/observe.js +45 -1
- package/dist/observe.js.map +1 -1
- package/dist/options.d.ts +53 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +3 -0
- package/dist/options.js.map +1 -1
- package/dist/reading-level.d.ts +22 -1
- package/dist/reading-level.d.ts.map +1 -1
- package/dist/reading-level.js +122 -19
- package/dist/reading-level.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +31 -15
- package/dist/reporter.js.map +1 -1
- package/dist/scan.d.ts +36 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +16 -2
- package/dist/scan.js.map +1 -1
- package/dist/screens.d.ts +55 -4
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +243 -43
- package/dist/screens.js.map +1 -1
- package/dist/settle.d.ts +35 -0
- package/dist/settle.d.ts.map +1 -1
- package/dist/settle.js +43 -2
- package/dist/settle.js.map +1 -1
- package/dist/summary.d.ts +35 -0
- package/dist/summary.d.ts.map +1 -1
- package/dist/summary.js +248 -28
- package/dist/summary.js.map +1 -1
- package/dist/unscannable-reasons.d.ts +30 -0
- package/dist/unscannable-reasons.d.ts.map +1 -0
- package/dist/unscannable-reasons.js +53 -0
- package/dist/unscannable-reasons.js.map +1 -0
- package/dist/viewports.d.ts +105 -0
- package/dist/viewports.d.ts.map +1 -0
- package/dist/viewports.js +134 -0
- package/dist/viewports.js.map +1 -0
- package/package.json +5 -4
package/NOTICE
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Setsquare
|
|
2
|
-
Copyright (c) 2026
|
|
2
|
+
Copyright (c) 2026 WORKTREE AMIGOS LTD (company no. 17430189)
|
|
3
3
|
|
|
4
4
|
This product includes software developed by the Setsquare project
|
|
5
5
|
(https://setsquare.dev).
|
|
@@ -39,7 +39,8 @@ the same thing. The accurate phrase is "Apache-2.0 licensed".
|
|
|
39
39
|
Trademarks
|
|
40
40
|
--------------------------------------------------------------------------------
|
|
41
41
|
|
|
42
|
-
"Setsquare" and the Setsquare logo are trademarks of
|
|
42
|
+
"Setsquare" and the Setsquare logo are unregistered trademarks of WORKTREE
|
|
43
|
+
AMIGOS LTD. No trademark registration is sought or held (decision D1). No
|
|
43
44
|
trademark licence is granted by the Apache License — section 6 reserves this
|
|
44
45
|
expressly — and none is granted by this notice. You may state accurately that
|
|
45
46
|
your software uses or is compatible with Setsquare; you may not use the name or
|
package/README.md
CHANGED
|
@@ -62,6 +62,14 @@ export default withSetsquare(defineConfig({/* your config, untouched */}));
|
|
|
62
62
|
|
|
63
63
|
Then add `.setsquare/` to your `.gitignore`.
|
|
64
64
|
|
|
65
|
+
**One install brings the `setsquare` command with it**, which is why there is no
|
|
66
|
+
second install line above. `@setsquare/review-cli` arrives as a dependency and
|
|
67
|
+
owns every command's implementation; the SDK owns the *name*. If you look, you
|
|
68
|
+
will find both packages declaring a `setsquare` binary, and that is deliberate
|
|
69
|
+
rather than a collision — pnpm links binaries only for **direct** dependencies,
|
|
70
|
+
so an SDK-only install would otherwise leave you with no `setsquare` at all.
|
|
71
|
+
Both resolve to the same code.
|
|
72
|
+
|
|
65
73
|
## What you get
|
|
66
74
|
|
|
67
75
|
Every settled page in your suite is scanned against **WCAG 2.2 AA**, and the run
|
|
@@ -85,6 +93,141 @@ and an evidence file at `.setsquare/runs/<timestamp>/run.json` — the full find
|
|
|
85
93
|
their WCAG criteria, measured contrast ratios, the exact ruleset used, and
|
|
86
94
|
performance timings.
|
|
87
95
|
|
|
96
|
+
### Checks that need the page driven, not read
|
|
97
|
+
|
|
98
|
+
Some of what runs is not axe. A DOM-snapshot analyser can only answer questions
|
|
99
|
+
about the document as it stands; several WCAG criteria are about what the page
|
|
100
|
+
*does*. These run because we are already inside your test, with your page:
|
|
101
|
+
|
|
102
|
+
| Criterion | What it does |
|
|
103
|
+
|---|---|
|
|
104
|
+
| **1.4.8** Visual Presentation, AAA | Measures line spacing, the measure in characters, and justification, on blocks of text. |
|
|
105
|
+
| **1.4.12** Text Spacing | Applies the SC's four spacing metrics and measures what clips. |
|
|
106
|
+
| **2.1.2** No Keyboard Trap | Tabs both ways and looks for a cycle focus never leaves. |
|
|
107
|
+
| **2.1.4** Character Key Shortcuts, opt-in | Watches which key handlers a page registers, and on which characters. |
|
|
108
|
+
| **2.4.7** Focus Visible | Diffs the computed style before and after focus — including `::before`/`::after`. |
|
|
109
|
+
| **2.4.11** Focus Not Obscured (Minimum) | Focuses each element and checks nothing covers it entirely. |
|
|
110
|
+
| **2.4.12** Focus Not Obscured (Enhanced), AAA | The same walk, stricter: no part of it covered. |
|
|
111
|
+
| **2.4.13** Focus Appearance, AAA | Measures the indicator's thickness and its contrast, and states both. |
|
|
112
|
+
| **3.1.5** Reading Level, AAA | A Flesch–Kincaid grade per page. |
|
|
113
|
+
| **3.2.1** On Focus | Focuses each control and watches for a change of context nobody asked for. |
|
|
114
|
+
| **3.2.2** On Input, opt-in | Changes each `<select>`, checkbox and radio and watches the same. |
|
|
115
|
+
| **3.2.3** Consistent Navigation | Compares navigation order **across surfaces**, after the run. |
|
|
116
|
+
| **3.2.4** Consistent Identification | Compares the names given to the same target across surfaces. |
|
|
117
|
+
| **3.2.6** Consistent Help | Compares where help mechanisms appear across surfaces. |
|
|
118
|
+
| **2.4.5** Multiple Ways | Looks across surfaces for navigation, a search control and a sitemap. |
|
|
119
|
+
| **3.3.1 / 4.1.3** Error Identification, Status Messages | Reads a page your test put into an error state. |
|
|
120
|
+
|
|
121
|
+
**If you use Tailwind and 1.4.8 fires across most of your prose, it is one line
|
|
122
|
+
of theme configuration rather than anything you wrote.** Tailwind's type scale
|
|
123
|
+
ships line heights below the 1.5 the SC asks for — `text-sm` is `20px` on a
|
|
124
|
+
`14px` font, which is 1.43 — and line height **inherits**, so one utility on one
|
|
125
|
+
container hands it to every paragraph inside, whatever size those paragraphs
|
|
126
|
+
are. One utility, dozens of findings. The fix is in `@theme`, once:
|
|
127
|
+
|
|
128
|
+
```css
|
|
129
|
+
@theme inline {
|
|
130
|
+
--text-xs--line-height: 1.55;
|
|
131
|
+
--text-sm--line-height: 1.55;
|
|
132
|
+
--text-base--line-height: 1.55;
|
|
133
|
+
--text-lg--line-height: 1.55;
|
|
134
|
+
--text-xl--line-height: 1.55;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
That raises the **default** and leaves `leading-tight` and friends working, so
|
|
139
|
+
headings and single-line labels keep the leading they are meant to have — which
|
|
140
|
+
matters, because 1.4.8 is about *blocks* of text and WCAG defines a block as
|
|
141
|
+
more than one sentence. 1.55 rather than 1.5 so a clamped font size cannot land
|
|
142
|
+
a block a hair under the bar. Worth checking before editing anything, because a
|
|
143
|
+
run of these usually has one cause rather than one per block.
|
|
144
|
+
|
|
145
|
+
The three walks that drive the page — focus, keyboard and reflow — run at
|
|
146
|
+
**teardown only**, and that is a real limitation rather than an implementation
|
|
147
|
+
detail. Focusing an element fires your page's own focus, focusin and blur
|
|
148
|
+
handlers; the keyboard walk presses Tab and Escape, which fires your key
|
|
149
|
+
handlers too; the reflow check resizes the viewport, which re-runs your media
|
|
150
|
+
queries. A handler that opened a menu or started a request has already run by
|
|
151
|
+
the time anything is restored.
|
|
152
|
+
|
|
153
|
+
Scroll position, the previously focused element and the viewport are all put
|
|
154
|
+
back; side effects cannot be. So a scan of a named state
|
|
155
|
+
(`{ state: 'menu-open' }`) carries no evidence from these checks, while
|
|
156
|
+
whatever your test left on screen does.
|
|
157
|
+
|
|
158
|
+
And twenty criteria are reported as **no automated rule can decide this** —
|
|
159
|
+
separately from the criteria nobody has built a rule for yet, because those are
|
|
160
|
+
different facts and an auditor's first question about an untested criterion is
|
|
161
|
+
which of the two it is. Each one carries the reason and what would have to
|
|
162
|
+
change for it to move, so the list is a position rather than a wall. Where
|
|
163
|
+
**presence** is checkable and **adequacy** is not — a caption track exists, but
|
|
164
|
+
whether it matches the audio is a judgement — the entry says so at that
|
|
165
|
+
granularity rather than writing the criterion off whole.
|
|
166
|
+
|
|
167
|
+
That list is the concrete floor under "automated evidence is not conformance".
|
|
168
|
+
A run that tells you twenty criteria no tool will ever check for you has told
|
|
169
|
+
you something more useful than a green tick.
|
|
170
|
+
|
|
171
|
+
Alongside those, eight **heuristics** point at criteria no rule can decide —
|
|
172
|
+
meaningful sequence (1.3.2), sensory instructions (1.3.3), weak headings
|
|
173
|
+
(2.4.6), unheaded runs (2.4.10), abbreviations (3.1.4), location cues (2.4.8),
|
|
174
|
+
missing caption tracks (1.2.1/1.2.2), and more than one route to a page
|
|
175
|
+
(2.4.5). They follow one discipline: **never a violation, always needs a
|
|
176
|
+
person, fire only above a margin, and say what would resolve it.** A heuristic
|
|
177
|
+
that reports a failure is worse than no heuristic, because someone who
|
|
178
|
+
disproves one stops trusting all of them.
|
|
179
|
+
|
|
180
|
+
They are on by default, being read-only and unable to fail a build, and each is
|
|
181
|
+
switchable on its own — `heuristics: { sensoryCharacteristics: false }` — so
|
|
182
|
+
disagreeing with one costs you one rather than all eight. The three that read
|
|
183
|
+
English words stay silent on a page in another language rather than guessing.
|
|
184
|
+
|
|
185
|
+
The last three below are the ones no per-page tool can have. They are statements
|
|
186
|
+
about a **set** of pages, computed after the whole run from what each scan
|
|
187
|
+
already carried — so they cost no extra page work, and they exist because the
|
|
188
|
+
run holds every surface at once.
|
|
189
|
+
|
|
190
|
+
They are also the ones with the most dangerous failure mode, so: **a run with
|
|
191
|
+
fewer than two surfaces reports them as untested, never as passing.** A
|
|
192
|
+
one-page run cannot evidence consistency, and a clean pass on a run that had
|
|
193
|
+
nothing to compare would be the most flattering possible lie. The summary says
|
|
194
|
+
"these criteria compare pages against each other — this run had one surface"
|
|
195
|
+
rather than folding them in with the criteria nothing can check.
|
|
196
|
+
|
|
197
|
+
Only links in navigation, header and footer landmarks are compared, because
|
|
198
|
+
3.2.4 is about repeated components: naming an inline prose link differently in
|
|
199
|
+
two sentences is ordinary writing, not an inconsistency.
|
|
200
|
+
|
|
201
|
+
**2.1.4 is off unless you turn it on** (`keyShortcuts: true`), and for a
|
|
202
|
+
different reason again. There is no way to enumerate a page's event listeners
|
|
203
|
+
after the fact, so the only route is to wrap `addEventListener` before your own
|
|
204
|
+
scripts run, for the whole life of the page. Everything else here happens after
|
|
205
|
+
a page has loaded and can be undone. Even proven inert — the wrapper records
|
|
206
|
+
and delegates with your original arguments, so `removeEventListener` still
|
|
207
|
+
matches, and the fixture suite asserts exactly that — it is a larger presence in
|
|
208
|
+
your application than a scanner has any business having by default.
|
|
209
|
+
|
|
210
|
+
It only ever reports **needs a person**: 2.1.4 is satisfied if the shortcut can
|
|
211
|
+
be turned off, remapped, or is only active when a component has focus, and all
|
|
212
|
+
three are affordances no check can see.
|
|
213
|
+
|
|
214
|
+
**3.2.2 is off unless you turn it on** (`onInput: true`), and the default is the
|
|
215
|
+
decision rather than caution: a change event is where an application saves, and
|
|
216
|
+
writing to your data is a worse failure than any finding the rule could produce
|
|
217
|
+
— worse than being a flake, because a flake is visible and a silent write is
|
|
218
|
+
not. Turn it on where the suite runs against disposable data, which is most
|
|
219
|
+
suites. Even then, only `<select>`, checkboxes and radios are touched and their
|
|
220
|
+
values are put back; nothing is ever typed into a text field, no form is ever
|
|
221
|
+
submitted, and no navigation is ever allowed to commit.
|
|
222
|
+
|
|
223
|
+
Where geometry or stacking makes the answer a judgement — a translucent overlay
|
|
224
|
+
over the focused element, an indicator drawn with a box-shadow, a focus ring
|
|
225
|
+
against a gradient — these report **needs a person**, never a pass and never a
|
|
226
|
+
fail. So does an element whose focus indicator we could not observe: many sites
|
|
227
|
+
style `:focus-visible` only, and a programmatic focus does not always match it,
|
|
228
|
+
so "nothing changed" is reported as something to check rather than as a
|
|
229
|
+
failure.
|
|
230
|
+
|
|
88
231
|
## Every screenshot on one page
|
|
89
232
|
|
|
90
233
|
Playwright takes the screenshots; reading them means opening its report and
|
|
@@ -151,6 +294,106 @@ test('the editor reports validation errors accessibly', async ({ page }) => {
|
|
|
151
294
|
|
|
152
295
|
An explicit scan always outranks an inferred one for the same context.
|
|
153
296
|
|
|
297
|
+
## Scanning at more than one viewport
|
|
298
|
+
|
|
299
|
+
A rule only ever sees the viewport it ran in. A navigation that collapses into a
|
|
300
|
+
menu below a breakpoint is a *different document*, not a narrower one, so a scan
|
|
301
|
+
at 1280 has no opinion about the same page at 390.
|
|
302
|
+
|
|
303
|
+
Setsquare cannot widen that for you — it scans the pages your suite drives, at
|
|
304
|
+
the sizes your suite drives them, and it takes no screenshots at all. A project
|
|
305
|
+
per width does, and it needs no changes to your tests:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
export default withSetsquare(
|
|
309
|
+
defineConfig({
|
|
310
|
+
projects: [
|
|
311
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
312
|
+
{
|
|
313
|
+
name: 'tablet',
|
|
314
|
+
testMatch: /a11y\.spec\.ts/,
|
|
315
|
+
use: { ...devices['Desktop Chrome'], viewport: { width: 768, height: 1024 } },
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'mobile',
|
|
319
|
+
testMatch: /a11y\.spec\.ts/,
|
|
320
|
+
use: { ...devices['Desktop Chrome'], viewport: { width: 390, height: 844 } },
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Spreading `Desktop Chrome` and overriding only `viewport` holds the engine
|
|
328
|
+
constant, so the width is the only variable — `devices['iPhone 15']` is WebKit
|
|
329
|
+
and changes both at once. `testMatch` keeps the extra projects to the specs whose
|
|
330
|
+
layout is the subject; a spec that sets its own viewport, or that checks request
|
|
331
|
+
origins or resolves links, returns the same answer at every width. And 320 needs
|
|
332
|
+
no project of its own: `setsquare-reflow` measures WCAG 1.4.10 there on every
|
|
333
|
+
surface already, by resizing at teardown and putting the viewport back.
|
|
334
|
+
|
|
335
|
+
The viewport is part of a scan's identity, so evidence from several widths
|
|
336
|
+
survives instead of collapsing into one row, and the report, the contact sheet
|
|
337
|
+
and the per-surface tables group by band — laptop 1024px and wider, tablet 768px
|
|
338
|
+
to 1023px, mobile narrower than 768px — with the exact size beside every name.
|
|
339
|
+
Those names describe a width and never a device: a 640px viewport is in the
|
|
340
|
+
mobile band whether it is a phone or a desktop browser with text at 200%, and
|
|
341
|
+
every artifact that prints a band name prints that caveat with it.
|
|
342
|
+
|
|
343
|
+
With one viewport in the run, Setsquare says nothing about narrow widths. Not
|
|
344
|
+
"no narrow-viewport failures found" — that would be a claim about your phone
|
|
345
|
+
layout from a run that never looked at it.
|
|
346
|
+
|
|
347
|
+
## Telling the report what your own tests already cover
|
|
348
|
+
|
|
349
|
+
Some success criteria no automated engine evaluates. Reflow at 320px, focus not
|
|
350
|
+
being obscured by a sticky header, a keyboard trap, whether the navigation is
|
|
351
|
+
consistent between pages — these are checked by driving the page, and a suite
|
|
352
|
+
that has grown up around a real product is usually checking several of them
|
|
353
|
+
already. Until you say so, the conformance report has no way to know: your spec
|
|
354
|
+
arrives as a pass or a fail with no criterion attached to it.
|
|
355
|
+
|
|
356
|
+
So say so:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
import { setsquare } from '@setsquare/review-sdk';
|
|
360
|
+
|
|
361
|
+
test('no horizontal scrolling at 320px', async ({ page }) => {
|
|
362
|
+
setsquare.covers('1.4.10');
|
|
363
|
+
|
|
364
|
+
await page.setViewportSize({ width: 320, height: 800 });
|
|
365
|
+
await page.goto('/pricing');
|
|
366
|
+
expect(await hasHorizontalOverflow(page)).toBe(false);
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
That criterion now appears in the report as **covered by your own test**, with
|
|
371
|
+
your test named next to it.
|
|
372
|
+
|
|
373
|
+
**It is recorded as your claim, not as our finding, and the report says so
|
|
374
|
+
everywhere it appears.** We do not check that a test declaring `1.4.10` tests
|
|
375
|
+
reflow — there is no way to — so a declaration is never merged into the state
|
|
376
|
+
that means every automated rule passed. It is its own state, worded
|
|
377
|
+
differently, with its own glyph in the grid. That is the point: the declaration
|
|
378
|
+
is exactly as strong as the test behind it, and the report never pretends
|
|
379
|
+
otherwise.
|
|
380
|
+
|
|
381
|
+
Three details worth knowing:
|
|
382
|
+
|
|
383
|
+
- **A declaring test that fails** is reported as *your own test for this
|
|
384
|
+
criterion did not pass*, and it stops the level reading as clean. It does not
|
|
385
|
+
become a detected failure: we did not observe it, and a red test can be red
|
|
386
|
+
for a reason that has nothing to do with the criterion.
|
|
387
|
+
- **A declaring test that is skipped** is reported as such, rather than folded
|
|
388
|
+
into "no automated check exists" — a check that exists and did not run is a
|
|
389
|
+
different problem.
|
|
390
|
+
- **An id that is not a WCAG 2.2 success criterion throws**, immediately, in
|
|
391
|
+
your test. This is the one thing in this package that does: everywhere else a
|
|
392
|
+
problem warns and carries on, because our evidence-gathering has no standing
|
|
393
|
+
to fail your build. A typo here is different — the alternative is dropping
|
|
394
|
+
your declaration silently, which would leave you believing you have coverage
|
|
395
|
+
you do not have.
|
|
396
|
+
|
|
154
397
|
## Configuration
|
|
155
398
|
|
|
156
399
|
Everything is optional. An empty options object produces a useful run.
|
|
@@ -164,17 +407,58 @@ export default withSetsquare(defineConfig({/* … */}), {
|
|
|
164
407
|
surfaces: { '/jobs/:id/edit': 'job-editor' },
|
|
165
408
|
mask: ['.candidate-name'],
|
|
166
409
|
outputDir: '.setsquare',
|
|
410
|
+
quietMs: 500, // how still counts as settled
|
|
411
|
+
settleTimeoutMs: 5000, // how long we wait for it
|
|
167
412
|
promo: false,
|
|
168
413
|
});
|
|
169
414
|
```
|
|
170
415
|
|
|
416
|
+
### When a page gets scanned, and the two knobs that decide it
|
|
417
|
+
|
|
418
|
+
Scans fire automatically when a page **settles**: `document.readyState` is
|
|
419
|
+
`complete`, and neither the URL nor the DOM has changed for `quietMs`. Any
|
|
420
|
+
mutation restarts that window, which is what makes the scanner wait for late
|
|
421
|
+
content instead of auditing a skeleton.
|
|
422
|
+
|
|
423
|
+
- **`quietMs`** (default `500`) — how long a page must be still. Shorter is not
|
|
424
|
+
better: measured against a route that renders a conformant shell and injects
|
|
425
|
+
its real content a second later, 250ms lands *inside* that gap and reports a
|
|
426
|
+
clean page. 500ms does not.
|
|
427
|
+
- **`settleTimeoutMs`** (default `5000`) — how long we wait for quiet before
|
|
428
|
+
scanning anyway. **It is a trigger, not a give-up.** A page that never goes
|
|
429
|
+
still — a poller, a carousel, a progress animation — is extremely common, and
|
|
430
|
+
declining to scan it would produce exactly the "no findings" that reads as "no
|
|
431
|
+
problems".
|
|
432
|
+
|
|
433
|
+
**What happens to a page that settles late.** If it is still on screen when the
|
|
434
|
+
test ends, it is scanned — at the ceiling, and again at teardown if it changed
|
|
435
|
+
in between, so the evidence describes the finished page. What cannot be
|
|
436
|
+
recovered is a surface the test **navigated away from** while it was still
|
|
437
|
+
rendering: it is gone before anything can look at it, and the run reports it as
|
|
438
|
+
visited-but-never-scanned rather than quietly dropping it. Raising
|
|
439
|
+
`settleTimeoutMs` does not help there — the limit is the test's pace, not ours.
|
|
440
|
+
|
|
441
|
+
**The report tells you which problem you have.** A run with scans cut short
|
|
442
|
+
names the longest wait and the number of DOM mutations still arriving. A page a
|
|
443
|
+
few mutations from quiet will settle if you give it longer; a page with
|
|
444
|
+
thousands never will, and no value of `settleTimeoutMs` changes that.
|
|
445
|
+
|
|
171
446
|
**Why AA by default.** It is what conformance means in practice, and what
|
|
172
447
|
EN 301 549 and procurement ask for. AAA is a deliberate commitment — WCAG itself
|
|
173
448
|
does not recommend it as a general policy — so opting in is the honest default.
|
|
174
449
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
450
|
+
**`personas`, `themes` and `surfaces` map your names to ours, and the direction
|
|
451
|
+
is the same in all three: the key is what is in your repository, the value is
|
|
452
|
+
the label you want in the report.** So `personas: { 'a11y-admin': 'recruiter' }`
|
|
453
|
+
means the Playwright **project** named `a11y-admin` is reported as the persona
|
|
454
|
+
`recruiter`; `themes: { 'dark-suite': 'dark' }` reads a project name and reports
|
|
455
|
+
a theme; and `surfaces` maps a URL pattern to a surface name. Keys are Playwright
|
|
456
|
+
project names in the first two and URL patterns in the third — nothing here maps
|
|
457
|
+
the other way round.
|
|
458
|
+
|
|
459
|
+
**Themes** are also read from `data-theme` on the live page when your suite
|
|
460
|
+
toggles them in-test, so a suite whose project names carry no theme still gets
|
|
461
|
+
one context per theme without configuring anything.
|
|
178
462
|
|
|
179
463
|
## One logical run across several Playwright invocations
|
|
180
464
|
|
package/dist/autoscan.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import type { BrowserContext, TestInfo } from '@playwright/test';
|
|
1
|
+
import type { BrowserContext, Page, TestInfo } from '@playwright/test';
|
|
2
|
+
import type { AxeFinding, RuleInapplicable, RulePass } from '@setsquare/schema';
|
|
2
3
|
import { type ResolvedSetsquareOptions } from './options.js';
|
|
3
4
|
export declare function installAutoScan(context: BrowserContext, options: ResolvedSetsquareOptions, testInfo: TestInfo, warn: (message: string) => void): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Setsquare's own driven checks: the ones that need the page acted on rather
|
|
7
|
+
* than read.
|
|
8
|
+
*
|
|
9
|
+
* Extracted so the teardown scan and the checks-only teardown record run
|
|
10
|
+
* exactly the same set. Two call sites with two hand-maintained lists is how a
|
|
11
|
+
* check ends up silently missing from half the runs.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runDrivenChecks(page: Page, options: ResolvedSetsquareOptions,
|
|
14
|
+
/**
|
|
15
|
+
* Had this page met the quiet definition when the checks ran?
|
|
16
|
+
*
|
|
17
|
+
* Defaults to true, so an explicit `setsquare.scan()` suppresses nothing: the
|
|
18
|
+
* customer chose the moment, and second-guessing their choice would be us
|
|
19
|
+
* deciding their page was not ready when they said it was.
|
|
20
|
+
*/
|
|
21
|
+
pageWentQuiet?: boolean): Promise<{
|
|
22
|
+
findings: AxeFinding[];
|
|
23
|
+
passes: RulePass[];
|
|
24
|
+
inapplicable: RuleInapplicable[];
|
|
25
|
+
}>;
|
|
4
26
|
/**
|
|
5
27
|
* Scan whatever the test left on screen, at teardown.
|
|
6
28
|
*
|
package/dist/autoscan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoscan.d.ts","sourceRoot":"","sources":["../src/autoscan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"autoscan.d.ts","sourceRoot":"","sources":["../src/autoscan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAoBhF,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAmGhF,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,wBAAwB,EACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,IAAI,CAAC,CAqFf;AAsMD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,wBAAwB;AACjC;;;;;;GAMG;AACH,aAAa,UAAO,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,YAAY,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAuC3F;AAuGD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,wBAAwB,EACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiGf"}
|