@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/dist/settle.js
CHANGED
|
@@ -26,9 +26,44 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Every scan carries its settle diagnostics, so a suspicious result is
|
|
28
28
|
* identifiable in the evidence rather than silently trusted.
|
|
29
|
+
*
|
|
30
|
+
* **WHAT HAPPENS TO A PAGE THAT SETTLES LATE**, recorded here because #137 asked
|
|
31
|
+
* whether it is scanned or dropped and observed that it was currently neither —
|
|
32
|
+
* an accident of where the ceiling happened to fall. It is not an accident, and
|
|
33
|
+
* the behaviour is worth stating rather than changing:
|
|
34
|
+
*
|
|
35
|
+
* - Still on screen when the test ends → **scanned, twice if needed.** The
|
|
36
|
+
* ceiling fires and scans the skeleton, then the teardown sweep re-scans
|
|
37
|
+
* because the mutation counter shows the page changed since. Dedupe keeps the
|
|
38
|
+
* better one. Proved rather than asserted: `fixtures/.../public.spec.ts`
|
|
39
|
+
* drives `/slow?delay=7000`, two seconds past the ceiling, and the surviving
|
|
40
|
+
* evidence carries both of that route's late violations.
|
|
41
|
+
* - Navigated away from mid-render → **cannot be scanned by anything.** It is
|
|
42
|
+
* gone before teardown arrives. Recorded in the unscannable ledger rather
|
|
43
|
+
* than dropped silently, and raising `settleTimeoutMs` does not help, because
|
|
44
|
+
* the limit there is the test's pace and not our window.
|
|
45
|
+
*
|
|
46
|
+
* So the ceiling costs no evidence on its own. What costs evidence is a test
|
|
47
|
+
* moving on, and that is the customer's to change or accept.
|
|
29
48
|
*/
|
|
30
49
|
/** Name of the page-side callback the SDK exposes. Namespaced; documented pollution. */
|
|
31
50
|
export const MUTATION_TOTAL = '__setsquareMutationTotal';
|
|
51
|
+
/**
|
|
52
|
+
* Whether this page has EVER met the quiet definition since its last navigation.
|
|
53
|
+
*
|
|
54
|
+
* Read at teardown, where no settle signal exists to consult: the teardown sweep
|
|
55
|
+
* scans whatever is on screen, and until this flag there was no way to tell a
|
|
56
|
+
* page that had finished rendering from one caught mid-flight. Both produced a
|
|
57
|
+
* scan that looked identical, and the checks making whole-page ABSENCE claims —
|
|
58
|
+
* "nothing on this page says where you are" — were as confident on one as the
|
|
59
|
+
* other. Friction record run 4 watched 2.4.8 flip between two runs of unchanged
|
|
60
|
+
* code for exactly that reason.
|
|
61
|
+
*
|
|
62
|
+
* A flag rather than a measurement, because measuring at teardown means sampling
|
|
63
|
+
* mutations, waiting `quietMs`, and sampling again — half a second added to every
|
|
64
|
+
* page of every test, to learn something the detector already knew.
|
|
65
|
+
*/
|
|
66
|
+
export const WENT_QUIET = '__setsquareWentQuiet';
|
|
32
67
|
export const SETTLE_BINDING = '__setsquareSettle';
|
|
33
68
|
export const DEFAULT_SETTLE = {
|
|
34
69
|
/*
|
|
@@ -83,6 +118,9 @@ export function settleScript(config) {
|
|
|
83
118
|
// Monotonic across the document lifetime, NOT reset per navigation: the
|
|
84
119
|
// teardown sweep uses it to decide whether a page changed after being scanned.
|
|
85
120
|
window.__setsquareMutationTotal = 0;
|
|
121
|
+
// Reset on every navigation below: "this page went quiet" must never be
|
|
122
|
+
// inherited by the next URL, which is the one way this flag could lie.
|
|
123
|
+
window.${WENT_QUIET} = false;
|
|
86
124
|
let via = 'load';
|
|
87
125
|
|
|
88
126
|
const report = (reason) => {
|
|
@@ -114,8 +152,10 @@ export function settleScript(config) {
|
|
|
114
152
|
// readyState is checked HERE rather than as a precondition, so a page that
|
|
115
153
|
// goes quiet while still loading keeps waiting instead of being scanned
|
|
116
154
|
// half-built.
|
|
117
|
-
if (document.readyState === 'complete')
|
|
118
|
-
|
|
155
|
+
if (document.readyState === 'complete') {
|
|
156
|
+
window.${WENT_QUIET} = true;
|
|
157
|
+
report('quiet');
|
|
158
|
+
} else scheduleQuiet();
|
|
119
159
|
}, QUIET);
|
|
120
160
|
};
|
|
121
161
|
|
|
@@ -124,6 +164,7 @@ export function settleScript(config) {
|
|
|
124
164
|
fired = false;
|
|
125
165
|
navigatedAt = Date.now();
|
|
126
166
|
mutations = 0;
|
|
167
|
+
window.${WENT_QUIET} = false;
|
|
127
168
|
clearTimeout(ceilingTimer);
|
|
128
169
|
ceilingTimer = setTimeout(() => report('ceiling'), CEILING);
|
|
129
170
|
scheduleQuiet();
|
package/dist/settle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../src/settle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../src/settle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,wFAAwF;AACxF,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAEjD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAqBlD,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C;;;;;;;OAOG;IACH,OAAO,EAAE,GAAG;IACZ;;;;;;;;OAQG;IACH,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO;;;;;;;kBAOS,MAAM,CAAC,OAAO;oBACZ,MAAM,CAAC,eAAe;;;;;;;;;;;;WAY/B,UAAU;;;;;;;;;;;;;0BAaK,cAAc;iBACvB,cAAc;;;;;;;;;;;;;;;;;;;iBAmBd,UAAU;;;;;;;;;;;aAWd,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCtB,CAAC;AACF,CAAC"}
|
package/dist/summary.d.ts
CHANGED
|
@@ -157,6 +157,21 @@ export interface SurfaceRow {
|
|
|
157
157
|
*/
|
|
158
158
|
incomplete: number;
|
|
159
159
|
rules: string[];
|
|
160
|
+
/**
|
|
161
|
+
* The same coordinates WITHOUT the viewport.
|
|
162
|
+
*
|
|
163
|
+
* The HTML report groups these rows into a row group per band and prints the
|
|
164
|
+
* band's sizes on its heading, so repeating the size on all thirty-six rows
|
|
165
|
+
* underneath is noise the grouping was supposed to remove. It is only noise
|
|
166
|
+
* where the band holds ONE size, which the renderer knows and this does not —
|
|
167
|
+
* so both strings are carried and the renderer picks.
|
|
168
|
+
*/
|
|
169
|
+
shortLabel: string;
|
|
170
|
+
/** The width this row was assessed at, when the run controlled one. */
|
|
171
|
+
viewport?: {
|
|
172
|
+
width: number;
|
|
173
|
+
height: number;
|
|
174
|
+
};
|
|
160
175
|
}
|
|
161
176
|
/**
|
|
162
177
|
* One row per scanned context, worst first.
|
|
@@ -175,6 +190,19 @@ export interface EvidenceQuality {
|
|
|
175
190
|
teardown: number;
|
|
176
191
|
/** Scans the settle ceiling cut short. Weaker evidence, and named as such. */
|
|
177
192
|
ceiling: number;
|
|
193
|
+
/**
|
|
194
|
+
* The longest a ceiling scan actually waited, and the most DOM mutations any
|
|
195
|
+
* of them counted.
|
|
196
|
+
*
|
|
197
|
+
* Both measured, neither configured. The note that reads these used to say
|
|
198
|
+
* "after waiting 5s", which is the DEFAULT — so a suite that raised
|
|
199
|
+
* `settleTimeoutMs` was told a number it had changed. And "a page that was
|
|
200
|
+
* still changing" without saying how much leaves the reader with one action
|
|
201
|
+
* ("raise the timeout") when there are two: a page 200 mutations from quiet
|
|
202
|
+
* will settle if given longer, and a page with a poller never will.
|
|
203
|
+
*/
|
|
204
|
+
ceilingWaitedMs: number;
|
|
205
|
+
ceilingMutations: number;
|
|
178
206
|
}
|
|
179
207
|
/**
|
|
180
208
|
* How much the findings are worth, as distinct from what they say.
|
|
@@ -193,6 +221,13 @@ export interface EvidenceQuality {
|
|
|
193
221
|
export declare function evidenceQuality(envelope: RunIngestRequest): EvidenceQuality;
|
|
194
222
|
export interface PerfRow {
|
|
195
223
|
label: string;
|
|
224
|
+
/** The same coordinates without the viewport — see `SurfaceRow.shortLabel`. */
|
|
225
|
+
shortLabel: string;
|
|
226
|
+
/** The width this page load happened at, when the run controlled one. */
|
|
227
|
+
viewport?: {
|
|
228
|
+
width: number;
|
|
229
|
+
height: number;
|
|
230
|
+
};
|
|
196
231
|
lcpMs?: number;
|
|
197
232
|
cls?: number;
|
|
198
233
|
loadMs?: number;
|
package/dist/summary.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAoBtE,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACxD;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CA+CxD;AAID,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4EAA4E;IAC5E,YAAY,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjD;;;;;OAKG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"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"}
|
package/dist/summary.js
CHANGED
|
@@ -40,12 +40,43 @@ export function groupLines(group) {
|
|
|
40
40
|
const IMPACT_ORDER = ['critical', 'serious', 'moderate', 'minor'];
|
|
41
41
|
/** Selectors shown per rule entry in the terminal; the full set is in report.html. */
|
|
42
42
|
const MAX_ENTRY_SELECTORS = 5;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
/**
|
|
44
|
+
* `job-editor · dark` — the coordinates a person can act on.
|
|
45
|
+
*
|
|
46
|
+
* The viewport joins them ONLY when the run holds more than one, and that
|
|
47
|
+
* condition is the whole design. Dedupe keys on the viewport unconditionally,
|
|
48
|
+
* because collapsing two widths into one row loses evidence; a label is read by
|
|
49
|
+
* a person, and `home · light · 1280×720` on every row of a suite that has one
|
|
50
|
+
* viewport is thirteen characters of noise repeated forty times. Where there
|
|
51
|
+
* genuinely are several, the label is the only thing telling two otherwise
|
|
52
|
+
* identical rows apart — and without it the surfaces table merges them by label
|
|
53
|
+
* and reports one surface's failures three times over.
|
|
54
|
+
*/
|
|
55
|
+
function contextLabel(context, withViewport = false) {
|
|
56
|
+
const viewport = withViewport && context.viewport
|
|
57
|
+
? `${context.viewport.width}\u00d7${context.viewport.height}`
|
|
58
|
+
: undefined;
|
|
59
|
+
return [context.surface, context.theme, context.persona, context.state, viewport]
|
|
46
60
|
.filter(Boolean)
|
|
47
61
|
.join(' · ');
|
|
48
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether this run looked at more than one viewport.
|
|
65
|
+
*
|
|
66
|
+
* Computed per envelope rather than carried on the scan, because it is a fact
|
|
67
|
+
* about the RUN: one scan cannot know whether it was the only width looked at.
|
|
68
|
+
*/
|
|
69
|
+
function hasSeveralViewports(envelope) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const scan of envelope.scans) {
|
|
72
|
+
const viewport = scan.context.viewport;
|
|
73
|
+
if (viewport)
|
|
74
|
+
seen.add(`${viewport.width}x${viewport.height}`);
|
|
75
|
+
if (seen.size > 1)
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
49
80
|
/**
|
|
50
81
|
* Collapse findings to one row per (rule, status).
|
|
51
82
|
*
|
|
@@ -55,11 +86,12 @@ function contextLabel(context) {
|
|
|
55
86
|
*/
|
|
56
87
|
export function groupFindings(envelope) {
|
|
57
88
|
const groups = new Map();
|
|
89
|
+
const withViewport = hasSeveralViewports(envelope);
|
|
58
90
|
for (const scan of envelope.scans) {
|
|
59
91
|
for (const finding of scan.findings) {
|
|
60
92
|
const key = `${finding.ruleId}\u0000${finding.status}`;
|
|
61
93
|
const existing = groups.get(key);
|
|
62
|
-
const label = contextLabel(scan.context);
|
|
94
|
+
const label = contextLabel(scan.context, withViewport);
|
|
63
95
|
if (existing) {
|
|
64
96
|
existing.nodes += finding.targets.nodeCount;
|
|
65
97
|
if (!existing.contexts.includes(label))
|
|
@@ -144,11 +176,25 @@ export function surfaceRows(envelope) {
|
|
|
144
176
|
* the tool repeating itself.
|
|
145
177
|
*/
|
|
146
178
|
const merged = new Map();
|
|
179
|
+
const withViewport = hasSeveralViewports(envelope);
|
|
147
180
|
for (const scan of envelope.scans) {
|
|
148
|
-
const label = contextLabel(scan.context);
|
|
181
|
+
const label = contextLabel(scan.context, withViewport);
|
|
149
182
|
const failing = scan.findings.filter((finding) => finding.status === 'violation');
|
|
150
183
|
const unresolved = scan.findings.filter((finding) => finding.status === 'incomplete');
|
|
151
|
-
const row = merged.get(label) ?? {
|
|
184
|
+
const row = merged.get(label) ?? {
|
|
185
|
+
label,
|
|
186
|
+
shortLabel: contextLabel(scan.context),
|
|
187
|
+
violations: 0,
|
|
188
|
+
incomplete: 0,
|
|
189
|
+
rules: [],
|
|
190
|
+
/*
|
|
191
|
+
* Carried as data as well as inside the label, because the report GROUPS
|
|
192
|
+
* on it. The label is for a person to read and a string cannot be grouped
|
|
193
|
+
* on without parsing it back, which is the kind of thing that works until
|
|
194
|
+
* a surface name contains a multiplication sign.
|
|
195
|
+
*/
|
|
196
|
+
...(scan.context.viewport ? { viewport: scan.context.viewport } : {}),
|
|
197
|
+
};
|
|
152
198
|
row.violations += failing.reduce((sum, finding) => sum + finding.targets.nodeCount, 0);
|
|
153
199
|
row.incomplete += unresolved.reduce((sum, finding) => sum + finding.targets.nodeCount, 0);
|
|
154
200
|
row.rules = [...new Set([...row.rules, ...failing.map((finding) => finding.ruleId)])].sort();
|
|
@@ -184,6 +230,8 @@ export function evidenceQuality(envelope) {
|
|
|
184
230
|
let quiet = 0;
|
|
185
231
|
let teardown = 0;
|
|
186
232
|
let ceiling = 0;
|
|
233
|
+
let ceilingWaitedMs = 0;
|
|
234
|
+
let ceilingMutations = 0;
|
|
187
235
|
for (const scan of envelope.scans) {
|
|
188
236
|
for (const pass of scan.passes ?? [])
|
|
189
237
|
rules.add(pass.ruleId);
|
|
@@ -200,12 +248,17 @@ export function evidenceQuality(envelope) {
|
|
|
200
248
|
break;
|
|
201
249
|
case 'ceiling':
|
|
202
250
|
ceiling += 1;
|
|
251
|
+
// The worst case rather than an average: one page pinned at the ceiling
|
|
252
|
+
// is the thing worth knowing about, and a mean over a healthy run would
|
|
253
|
+
// hide it.
|
|
254
|
+
ceilingWaitedMs = Math.max(ceilingWaitedMs, scan.settle.sinceNavMs);
|
|
255
|
+
ceilingMutations = Math.max(ceilingMutations, scan.settle.mutations);
|
|
203
256
|
break;
|
|
204
257
|
default:
|
|
205
258
|
break;
|
|
206
259
|
}
|
|
207
260
|
}
|
|
208
|
-
return { rulesAssessed: rules.size, quiet, teardown, ceiling };
|
|
261
|
+
return { rulesAssessed: rules.size, quiet, teardown, ceiling, ceilingWaitedMs, ceilingMutations };
|
|
209
262
|
}
|
|
210
263
|
/*
|
|
211
264
|
* Google's "good" thresholds for the two Core Web Vitals we can measure in a lab.
|
|
@@ -231,14 +284,17 @@ const GOOD_CLS = 0.1;
|
|
|
231
284
|
*/
|
|
232
285
|
export function perfRows(envelope) {
|
|
233
286
|
const byVisit = new Map();
|
|
287
|
+
const withViewport = hasSeveralViewports(envelope);
|
|
234
288
|
for (const scan of envelope.scans) {
|
|
235
289
|
if (!scan.perf)
|
|
236
290
|
continue;
|
|
237
|
-
const key = scan.visitRef ?? `${scan.testId}:${contextLabel(scan.context)}`;
|
|
291
|
+
const key = scan.visitRef ?? `${scan.testId}:${contextLabel(scan.context, withViewport)}`;
|
|
238
292
|
if (byVisit.has(key))
|
|
239
293
|
continue;
|
|
240
294
|
byVisit.set(key, {
|
|
241
|
-
label: contextLabel(scan.context),
|
|
295
|
+
label: contextLabel(scan.context, withViewport),
|
|
296
|
+
shortLabel: contextLabel(scan.context),
|
|
297
|
+
...(scan.context.viewport ? { viewport: scan.context.viewport } : {}),
|
|
242
298
|
...(scan.perf.lcpMs !== undefined ? { lcpMs: scan.perf.lcpMs } : {}),
|
|
243
299
|
...(scan.perf.cls !== undefined ? { cls: scan.perf.cls } : {}),
|
|
244
300
|
...(scan.perf.loadMs !== undefined ? { loadMs: scan.perf.loadMs } : {}),
|
|
@@ -484,8 +540,27 @@ export function renderText(input, columns) {
|
|
|
484
540
|
*/
|
|
485
541
|
lines.push(heading(style, 'Accessibility'));
|
|
486
542
|
lines.push(figureLine(style, 0, 'surfaces scanned'));
|
|
487
|
-
|
|
488
|
-
|
|
543
|
+
/*
|
|
544
|
+
* Name the cause when the run knows it, and only then.
|
|
545
|
+
*
|
|
546
|
+
* "Usually the suite never opened a page, or every test finished before its
|
|
547
|
+
* page settled" is a guess offered in place of a fact the envelope is
|
|
548
|
+
* holding. On a lane run with `SETSQUARE_AUTOSCAN=0` both halves of that
|
|
549
|
+
* sentence are false and both describe a broken suite, which is how a
|
|
550
|
+
* perfectly healthy functional lane came to read as a failure.
|
|
551
|
+
*
|
|
552
|
+
* "This is not a clean result" stays either way. Nothing was looked at, and
|
|
553
|
+
* that is true whether or not the reason is reassuring.
|
|
554
|
+
*/
|
|
555
|
+
const offRows = (envelope.unscanned ?? []).filter((row) => row.reason === 'autoscan-disabled');
|
|
556
|
+
const allOff = offRows.length > 0 && offRows.length === (envelope.unscanned ?? []).length;
|
|
557
|
+
lines.push(...continuationLines(style, allOff
|
|
558
|
+
? 'This is not a clean result — it means nothing was looked at. Automatic ' +
|
|
559
|
+
'scanning was off for this run (SETSQUARE_AUTOSCAN=0), so the surfaces ' +
|
|
560
|
+
'below were visited and never scanned. That is the configuration working ' +
|
|
561
|
+
'as asked; it is not a fault in the suite.'
|
|
562
|
+
: 'This is not a clean result — it means nothing was looked at. Usually the ' +
|
|
563
|
+
'suite never opened a page, or every test finished before its page settled.', width));
|
|
489
564
|
lines.push(...trailer(false));
|
|
490
565
|
return join(lines);
|
|
491
566
|
}
|
|
@@ -829,6 +904,17 @@ function conformanceLines(input, style, width) {
|
|
|
829
904
|
const criterionRow = (rollup) => [
|
|
830
905
|
figureLine(style, rollup.failedSurfaces.length, `${style.bold(`${rollup.criterion} ${rollup.name}`)} ${style.dim(`FAIL on ${surfaceShare(rollup.failedSurfaces.length, rollup.totalSurfaces)}`)}`),
|
|
831
906
|
...continuationLines(style, rollup.failingRules.join(', '), width),
|
|
907
|
+
/*
|
|
908
|
+
* A cross-surface criterion has no failing RULE to name — the finding is
|
|
909
|
+
* the comparison itself — so the row would otherwise be a bare percentage.
|
|
910
|
+
* Both disagreeing surfaces are named, because "the navigation is
|
|
911
|
+
* inconsistent" without saying between which two pages sends the reader to
|
|
912
|
+
* look at everything.
|
|
913
|
+
*/
|
|
914
|
+
...(rollup.consistency?.disagreements ?? [])
|
|
915
|
+
.slice(0, 2)
|
|
916
|
+
.flatMap((row) => continuationLines(style, row.detail, width)),
|
|
917
|
+
...(rollup.consistency?.note ? continuationLines(style, rollup.consistency.note, width) : []),
|
|
832
918
|
];
|
|
833
919
|
for (const level of summary.levels) {
|
|
834
920
|
if (level.level === 'AAA' && !level.assessed) {
|
|
@@ -849,9 +935,28 @@ function conformanceLines(input, style, width) {
|
|
|
849
935
|
for (const rollup of level.failing)
|
|
850
936
|
lines.push(...criterionRow(rollup));
|
|
851
937
|
}
|
|
938
|
+
else if (level.assertedFailing.length > 0) {
|
|
939
|
+
// Not "no failures detected": a level whose own declared test is red is
|
|
940
|
+
// neither clean nor failed, and saying "no failures detected" over the
|
|
941
|
+
// top of it would be the dishonest direction.
|
|
942
|
+
lines.push(figureLine(style, level.assertedFailing.length, `Level ${level.level.padEnd(3)} no failures detected, but ` +
|
|
943
|
+
`${level.assertedFailing.length === 1 ? 'a criterion your suite covers' : `${level.assertedFailing.length} criteria your suite covers`} did not pass`));
|
|
944
|
+
}
|
|
852
945
|
else {
|
|
853
946
|
lines.push(figureLine(style, '', `Level ${level.level.padEnd(3)} no failures detected in what was scanned`));
|
|
854
947
|
}
|
|
948
|
+
/*
|
|
949
|
+
* Listed whether or not the level also has detected failures. These are
|
|
950
|
+
* the customer's own assertions, so the row says whose claim it is and
|
|
951
|
+
* names the test — the only useful next action is opening that file.
|
|
952
|
+
*/
|
|
953
|
+
for (const rollup of level.assertedFailing) {
|
|
954
|
+
lines.push(...continuationLines(style, `${rollup.criterion} ${rollup.name} — your own test for it did not pass` +
|
|
955
|
+
(rollup.assertions && rollup.assertions.notUpheld.length > 0
|
|
956
|
+
? `: ${rollup.assertions.notUpheld.join(', ')}`
|
|
957
|
+
: '') +
|
|
958
|
+
'. Setsquare did not detect this; your suite declared it.', width));
|
|
959
|
+
}
|
|
855
960
|
if (level.assessedScans) {
|
|
856
961
|
lines.push(...continuationLines(style, `AAA was assessed on ${level.assessedScans.aaa} of ${level.assessedScans.total} scans — ` +
|
|
857
962
|
'the rest looked with AA rules only.', width));
|
|
@@ -887,6 +992,15 @@ function conformanceLines(input, style, width) {
|
|
|
887
992
|
sentence += ` Then for AAA: ${ids('AAA')}.`;
|
|
888
993
|
lines.push(...continuationLines(style, sentence, width));
|
|
889
994
|
}
|
|
995
|
+
else if (verdict.assertionsFailingAt) {
|
|
996
|
+
// The third outcome. Neither "we found failures" — we found none — nor
|
|
997
|
+
// "no failures detected", which would be publishing a clean line over the
|
|
998
|
+
// top of the suite's own red check.
|
|
999
|
+
lines.push(...continuationLines(style, `No Level ${verdict.assertionsFailingAt} violations detected, but ` +
|
|
1000
|
+
`${verdict.failingAssertions.length === 1 ? 'a criterion your suite declares it covers' : `${verdict.failingAssertions.length} criteria your suite declares it covers`} ` +
|
|
1001
|
+
`did not pass: ${verdict.failingAssertions.map((r) => r.criterion).join(', ')}. ` +
|
|
1002
|
+
'That is your assertion, not our finding — but nothing here says the level is clean.', width));
|
|
1003
|
+
}
|
|
890
1004
|
else if (verdict.noFailuresDetectedAt) {
|
|
891
1005
|
lines.push(...continuationLines(style, `No Level ${verdict.noFailuresDetectedAt} violations detected across ` +
|
|
892
1006
|
`${n(summary.surfacesScanned)} surface${summary.surfacesScanned === 1 ? '' : 's'} ` +
|
|
@@ -902,6 +1016,7 @@ function conformanceLines(input, style, width) {
|
|
|
902
1016
|
const rulesPassed = counted.reduce((sum, level) => sum + level.rulesPassed.length, 0);
|
|
903
1017
|
const cantTell = counted.reduce((sum, level) => sum + level.cantTell.length, 0);
|
|
904
1018
|
const inapplicable = counted.reduce((sum, level) => sum + level.inapplicable.length, 0);
|
|
1019
|
+
const assertedBySuite = counted.flatMap((level) => level.assertedBySuite);
|
|
905
1020
|
const untested = counted.flatMap((level) => level.untested);
|
|
906
1021
|
if (rulesPassed > 0) {
|
|
907
1022
|
lines.push(figureLine(style, rulesPassed, 'criteria: every automated rule passed — evidence, not conformance'));
|
|
@@ -914,17 +1029,69 @@ function conformanceLines(input, style, width) {
|
|
|
914
1029
|
// nothing these criteria regulate — coverage, not a gap.
|
|
915
1030
|
lines.push(figureLine(style, inapplicable, 'criteria: checks ran, nothing to check on these pages (no such content)'));
|
|
916
1031
|
}
|
|
1032
|
+
if (assertedBySuite.length > 0) {
|
|
1033
|
+
/*
|
|
1034
|
+
* Deliberately worded as the customer's claim and placed AFTER the
|
|
1035
|
+
* rule-based figures, because it is the weakest positive line on the
|
|
1036
|
+
* summary. It is also the only line here whose strength we cannot assess:
|
|
1037
|
+
* it is exactly as good as the tests behind it.
|
|
1038
|
+
*/
|
|
1039
|
+
lines.push(figureLine(style, assertedBySuite.length, 'criteria covered by your own tests — your assertion, no rule ran'));
|
|
1040
|
+
}
|
|
917
1041
|
if (untested.length > 0) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1042
|
+
const noRule = untested.filter((r) => r.untestedReason === 'noRuleCanDecide');
|
|
1043
|
+
if (noRule.length > 0) {
|
|
1044
|
+
/*
|
|
1045
|
+
* The most useful line on this block, and the one the product's central
|
|
1046
|
+
* claim rests on. "No automated rule can decide these, whatever anyone
|
|
1047
|
+
* builds" is a concrete floor under "automated evidence is not
|
|
1048
|
+
* conformance" — and a customer who sees it understands what they still
|
|
1049
|
+
* owe an auditor, which is a better thing to have told them than a tick.
|
|
1050
|
+
*/
|
|
1051
|
+
lines.push(figureLine(style, noRule.length, 'criteria no automated rule can decide — what you still owe an auditor'));
|
|
925
1052
|
}
|
|
926
|
-
|
|
927
|
-
|
|
1053
|
+
const compared = untested.filter((r) => r.untestedReason === 'singleSurface');
|
|
1054
|
+
if (compared.length > 0) {
|
|
1055
|
+
/*
|
|
1056
|
+
* Its own line, and never folded into "no automated check". These
|
|
1057
|
+
* criteria HAVE a check; fewer than two pages carried the thing it
|
|
1058
|
+
* compares, so there was nothing to compare. Reporting them as
|
|
1059
|
+
* uncheckable would be wrong in the direction that sells the product
|
|
1060
|
+
* short — one more page with the evidence on it is all they need.
|
|
1061
|
+
*
|
|
1062
|
+
* THE WORDING USED TO SAY "this run had one surface", AND THAT WAS A
|
|
1063
|
+
* CLAIM, not a description. It printed unchanged on an eighteen-surface
|
|
1064
|
+
* run of this repository's own site, two lines under "18 surfaces
|
|
1065
|
+
* scanned" — because the reason is fewer than two surfaces carrying the
|
|
1066
|
+
* EVIDENCE, not fewer than two surfaces. 3.2.6 needs at least two pages
|
|
1067
|
+
* that actually offer a help mechanism; a hundred pages without one
|
|
1068
|
+
* still give it nothing to compare. The schema's own note on
|
|
1069
|
+
* `singleSurface` had it right all along.
|
|
1070
|
+
*/
|
|
1071
|
+
lines.push(figureLine(style, compared.length, 'criteria compare pages against each other — fewer than two pages carried the evidence'));
|
|
1072
|
+
}
|
|
1073
|
+
const skipped = untested.filter((r) => r.untestedReason === 'declarationSkipped').length;
|
|
1074
|
+
if (skipped > 0) {
|
|
1075
|
+
// Not folded into "no automated check": the criterion had a check, it
|
|
1076
|
+
// was the suite's own, and it did not run. That is a suite problem, and
|
|
1077
|
+
// reporting it as a tooling limitation would hide it.
|
|
1078
|
+
lines.push(figureLine(style, skipped, 'criteria your suite covers were skipped and did not run'));
|
|
1079
|
+
}
|
|
1080
|
+
const rest = untested.filter((r) => r.untestedReason !== 'declarationSkipped' &&
|
|
1081
|
+
r.untestedReason !== 'singleSurface' &&
|
|
1082
|
+
r.untestedReason !== 'noRuleCanDecide');
|
|
1083
|
+
if (rest.length > 0) {
|
|
1084
|
+
if (input.criterionRules) {
|
|
1085
|
+
const noCheck = rest.filter((r) => r.untestedReason === 'notAutomatable').length;
|
|
1086
|
+
const notRun = rest.filter((r) => r.untestedReason === 'notRun').length;
|
|
1087
|
+
if (noCheck > 0)
|
|
1088
|
+
lines.push(figureLine(style, noCheck, 'criteria have no automated check in this engine'));
|
|
1089
|
+
if (notRun > 0)
|
|
1090
|
+
lines.push(figureLine(style, notRun, 'criteria have checks that did not run here'));
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
lines.push(figureLine(style, rest.length, 'criteria not automatically checked in this run'));
|
|
1094
|
+
}
|
|
928
1095
|
}
|
|
929
1096
|
}
|
|
930
1097
|
const anyFailure = summary.levels.some((level) => level.failing.length > 0);
|
|
@@ -966,9 +1133,31 @@ function gridLines(input, style, width) {
|
|
|
966
1133
|
// ASCII glyphs whenever styling is off: the same environments that lose
|
|
967
1134
|
// colour (CI, pipes, NO_COLOR) are the ones where glyph rendering is least
|
|
968
1135
|
// trustworthy, and the fallback must be decided by something a test can set.
|
|
1136
|
+
//
|
|
1137
|
+
// The two declared states get their own glyphs and never borrow ✓ or ✗.
|
|
1138
|
+
// A cell that means "your test says so" must not look like a cell that
|
|
1139
|
+
// means "a rule ran" — that flattening is the exact thing the state was
|
|
1140
|
+
// added to prevent, and a shared glyph would reintroduce it in the one
|
|
1141
|
+
// rendering most people actually read.
|
|
969
1142
|
const glyph = style.on
|
|
970
|
-
? {
|
|
971
|
-
|
|
1143
|
+
? {
|
|
1144
|
+
failed: '✗',
|
|
1145
|
+
assertedFailing: '⊗',
|
|
1146
|
+
cantTell: '?',
|
|
1147
|
+
rulesPassed: '✓',
|
|
1148
|
+
inapplicable: '–',
|
|
1149
|
+
assertedBySuite: '⊙',
|
|
1150
|
+
untested: '·',
|
|
1151
|
+
}
|
|
1152
|
+
: {
|
|
1153
|
+
failed: 'F',
|
|
1154
|
+
assertedFailing: 'f',
|
|
1155
|
+
cantTell: '?',
|
|
1156
|
+
rulesPassed: 'P',
|
|
1157
|
+
inapplicable: '-',
|
|
1158
|
+
assertedBySuite: 'y',
|
|
1159
|
+
untested: '.',
|
|
1160
|
+
};
|
|
972
1161
|
const indent = ' ';
|
|
973
1162
|
const idWidth = Math.max(...grid.rows.map((row) => row.criterion.length));
|
|
974
1163
|
const nameWidth = Math.min(Math.max(...grid.rows.map((row) => row.name.length)), 28);
|
|
@@ -998,7 +1187,10 @@ function gridLines(input, style, width) {
|
|
|
998
1187
|
lines.push('');
|
|
999
1188
|
lines.push(...continuationLines(style, `${glyph.failed} failed ${glyph.cantTell} needs a person ` +
|
|
1000
1189
|
`${glyph.rulesPassed} every automated rule passed ` +
|
|
1001
|
-
`${glyph.inapplicable} nothing to check here
|
|
1190
|
+
`${glyph.inapplicable} nothing to check here ` +
|
|
1191
|
+
`${glyph.assertedBySuite} covered by your own test ` +
|
|
1192
|
+
`${glyph.assertedFailing} your own test for it did not pass ` +
|
|
1193
|
+
`${glyph.untested} untested`, width));
|
|
1002
1194
|
lines.push(...continuationLines(style, grid.surfaces.map((surface, i) => `${i + 1} ${surface}`).join(' '), width));
|
|
1003
1195
|
return lines;
|
|
1004
1196
|
}
|
|
@@ -1032,9 +1224,28 @@ function warningLines(warnings, width, quality, envelope) {
|
|
|
1032
1224
|
* dying in the envelope.
|
|
1033
1225
|
*/
|
|
1034
1226
|
if (quality && quality.ceiling > 0) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1227
|
+
/*
|
|
1228
|
+
* Say WHOSE window it was, and WHAT the page was doing in it.
|
|
1229
|
+
*
|
|
1230
|
+
* The old sentence said "after waiting 5s", which is the default — a suite
|
|
1231
|
+
* that raised `settleTimeoutMs` was quoted a number it had changed. And it
|
|
1232
|
+
* described "a page that was still changing" without the one figure that
|
|
1233
|
+
* tells a reader which of the two available actions is theirs: a page a few
|
|
1234
|
+
* mutations from quiet settles if given longer, and a page with a poller
|
|
1235
|
+
* never will, and no timeout helps the second.
|
|
1236
|
+
*
|
|
1237
|
+
* Both numbers are measured off the scans themselves rather than read from
|
|
1238
|
+
* configuration, which also means they stay true if the window is changed
|
|
1239
|
+
* per project.
|
|
1240
|
+
*/
|
|
1241
|
+
const waited = `${(quality.ceilingWaitedMs / 1000).toFixed(1)}s`;
|
|
1242
|
+
notes.push(`${quality.ceiling} scan${quality.ceiling === 1 ? ' was' : 's were'} cut short by our ` +
|
|
1243
|
+
`settle window — the longest waited ${waited} and the page was still changing, with ` +
|
|
1244
|
+
`${quality.ceilingMutations.toLocaleString('en-GB')} DOM mutation` +
|
|
1245
|
+
`${quality.ceilingMutations === 1 ? '' : 's'} counted since it loaded. Treat a clean ` +
|
|
1246
|
+
'result from those as weaker evidence. If the page settles and simply needs longer, ' +
|
|
1247
|
+
'raise `settleTimeoutMs`; if something polls or animates forever, no window will help ' +
|
|
1248
|
+
'and the scan is of a moving page by nature.');
|
|
1038
1249
|
}
|
|
1039
1250
|
if (warnings.length === 0 && notes.length === 0)
|
|
1040
1251
|
return [];
|
|
@@ -1105,16 +1316,25 @@ function markdownConformance(input) {
|
|
|
1105
1316
|
}
|
|
1106
1317
|
lines.push('');
|
|
1107
1318
|
const withEvidence = summary.levels.flatMap((level) => level.level === 'AAA' && !level.assessed
|
|
1108
|
-
? level.failing
|
|
1109
|
-
: [
|
|
1319
|
+
? [...level.failing, ...level.assertedFailing]
|
|
1320
|
+
: [
|
|
1321
|
+
...level.failing,
|
|
1322
|
+
...level.assertedFailing,
|
|
1323
|
+
...level.cantTell,
|
|
1324
|
+
...level.rulesPassed,
|
|
1325
|
+
...level.inapplicable,
|
|
1326
|
+
...level.assertedBySuite,
|
|
1327
|
+
]);
|
|
1110
1328
|
if (withEvidence.length > 0) {
|
|
1111
1329
|
lines.push('| Criterion | Level | State | Surfaces |');
|
|
1112
1330
|
lines.push('|---|---|---|---|');
|
|
1113
1331
|
const stateWords = {
|
|
1114
1332
|
failed: 'FAIL',
|
|
1333
|
+
assertedFailing: 'your test for it did not pass',
|
|
1115
1334
|
cantTell: 'needs a person',
|
|
1116
1335
|
rulesPassed: 'rules passed',
|
|
1117
1336
|
inapplicable: 'nothing to check',
|
|
1337
|
+
assertedBySuite: 'covered by your own test',
|
|
1118
1338
|
untested: 'untested',
|
|
1119
1339
|
};
|
|
1120
1340
|
for (const rollup of [...withEvidence].sort((a, b) => a.criterion.localeCompare(b.criterion, 'en', { numeric: true }))) {
|