@setsquare/review-sdk 0.0.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/LICENSE +202 -0
- package/NOTICE +57 -0
- package/README.md +338 -0
- package/THIRD-PARTY-NOTICES.md +61 -0
- package/dist/autoscan.d.ts +25 -0
- package/dist/autoscan.d.ts.map +1 -0
- package/dist/autoscan.js +332 -0
- package/dist/autoscan.js.map +1 -0
- package/dist/axe.d.ts +96 -0
- package/dist/axe.d.ts.map +1 -0
- package/dist/axe.js +210 -0
- package/dist/axe.js.map +1 -0
- package/dist/bin/setsquare.d.ts +3 -0
- package/dist/bin/setsquare.d.ts.map +1 -0
- package/dist/bin/setsquare.js +21 -0
- package/dist/bin/setsquare.js.map +1 -0
- package/dist/brand.d.ts +84 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +335 -0
- package/dist/brand.js.map +1 -0
- package/dist/checks/dynamic.d.ts +57 -0
- package/dist/checks/dynamic.d.ts.map +1 -0
- package/dist/checks/dynamic.js +184 -0
- package/dist/checks/dynamic.js.map +1 -0
- package/dist/checks/rule-pack.d.ts +34 -0
- package/dist/checks/rule-pack.d.ts.map +1 -0
- package/dist/checks/rule-pack.js +270 -0
- package/dist/checks/rule-pack.js.map +1 -0
- package/dist/checks/versions.d.ts +28 -0
- package/dist/checks/versions.d.ts.map +1 -0
- package/dist/checks/versions.js +45 -0
- package/dist/checks/versions.js.map +1 -0
- package/dist/ci.d.ts +37 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +199 -0
- package/dist/ci.js.map +1 -0
- package/dist/contract.d.ts +334 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +35 -0
- package/dist/contract.js.map +1 -0
- package/dist/dedupe.d.ts +79 -0
- package/dist/dedupe.d.ts.map +1 -0
- package/dist/dedupe.js +102 -0
- package/dist/dedupe.js.map +1 -0
- package/dist/document.d.ts +29 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +94 -0
- package/dist/document.js.map +1 -0
- package/dist/envelope.d.ts +124 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +337 -0
- package/dist/envelope.js.map +1 -0
- package/dist/escape.d.ts +14 -0
- package/dist/escape.d.ts.map +1 -0
- package/dist/escape.js +20 -0
- package/dist/escape.js.map +1 -0
- package/dist/findings.d.ts +47 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +229 -0
- package/dist/findings.js.map +1 -0
- package/dist/fixture.d.ts +7 -0
- package/dist/fixture.d.ts.map +1 -0
- package/dist/fixture.js +237 -0
- package/dist/fixture.js.map +1 -0
- package/dist/group.d.ts +214 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +709 -0
- package/dist/group.js.map +1 -0
- package/dist/html-report.d.ts +68 -0
- package/dist/html-report.d.ts.map +1 -0
- package/dist/html-report.js +649 -0
- package/dist/html-report.js.map +1 -0
- package/dist/human.d.ts +65 -0
- package/dist/human.d.ts.map +1 -0
- package/dist/human.js +111 -0
- package/dist/human.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +65 -0
- package/dist/infer.d.ts.map +1 -0
- package/dist/infer.js +67 -0
- package/dist/infer.js.map +1 -0
- package/dist/insights.d.ts +140 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +483 -0
- package/dist/insights.js.map +1 -0
- package/dist/install.d.ts +8 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +114 -0
- package/dist/install.js.map +1 -0
- package/dist/links.d.ts +45 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +45 -0
- package/dist/links.js.map +1 -0
- package/dist/mask.d.ts +85 -0
- package/dist/mask.d.ts.map +1 -0
- package/dist/mask.js +98 -0
- package/dist/mask.js.map +1 -0
- package/dist/observe.d.ts +51 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +233 -0
- package/dist/observe.js.map +1 -0
- package/dist/options.d.ts +249 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +185 -0
- package/dist/options.js.map +1 -0
- package/dist/output.d.ts +113 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +231 -0
- package/dist/output.js.map +1 -0
- package/dist/perf.d.ts +11 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +150 -0
- package/dist/perf.js.map +1 -0
- package/dist/reading-level.d.ts +73 -0
- package/dist/reading-level.d.ts.map +1 -0
- package/dist/reading-level.js +189 -0
- package/dist/reading-level.js.map +1 -0
- package/dist/reporter.d.ts +94 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +709 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules.d.ts +55 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +77 -0
- package/dist/rules.js.map +1 -0
- package/dist/runtime.d.ts +32 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +67 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sanitise.d.ts +116 -0
- package/dist/sanitise.d.ts.map +1 -0
- package/dist/sanitise.js +247 -0
- package/dist/sanitise.js.map +1 -0
- package/dist/scan.d.ts +60 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +246 -0
- package/dist/scan.js.map +1 -0
- package/dist/screens.d.ts +98 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +301 -0
- package/dist/screens.js.map +1 -0
- package/dist/settle.d.ts +65 -0
- package/dist/settle.d.ts.map +1 -0
- package/dist/settle.js +162 -0
- package/dist/settle.js.map +1 -0
- package/dist/shots.d.ts +109 -0
- package/dist/shots.d.ts.map +1 -0
- package/dist/shots.js +113 -0
- package/dist/shots.js.map +1 -0
- package/dist/style.d.ts +47 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +154 -0
- package/dist/style.js.map +1 -0
- package/dist/suite.d.ts +218 -0
- package/dist/suite.d.ts.map +1 -0
- package/dist/suite.js +219 -0
- package/dist/suite.js.map +1 -0
- package/dist/summary.d.ts +268 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +1308 -0
- package/dist/summary.js.map +1 -0
- package/dist/unscannable.d.ts +75 -0
- package/dist/unscannable.d.ts.map +1 -0
- package/dist/unscannable.js +111 -0
- package/dist/unscannable.js.map +1 -0
- package/dist/versions.d.ts +13 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +39 -0
- package/dist/versions.js.map +1 -0
- package/dist/wcag-map.d.ts +19 -0
- package/dist/wcag-map.d.ts.map +1 -0
- package/dist/wcag-map.js +52 -0
- package/dist/wcag-map.js.map +1 -0
- package/dist/with-setsquare.d.ts +26 -0
- package/dist/with-setsquare.d.ts.map +1 -0
- package/dist/with-setsquare.js +70 -0
- package/dist/with-setsquare.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import readability from 'text-readability';
|
|
2
|
+
import { sanitiseComputed } from './findings.js';
|
|
3
|
+
import { READING_LEVEL_HELP_URL } from './links.js';
|
|
4
|
+
/**
|
|
5
|
+
* WCAG 3.1.5 Reading Level (AAA), as an honest heuristic.
|
|
6
|
+
*
|
|
7
|
+
* The SC's normative bar is text readable "without lower secondary education"
|
|
8
|
+
* — ISCED level 2, roughly nine years of schooling — measured *after removing
|
|
9
|
+
* proper names and titles*, and satisfiable by supplemental content or a
|
|
10
|
+
* simpler alternate version. No formula measures any of that. What formulas
|
|
11
|
+
* measure is word and sentence statistics, which approximate a reading grade,
|
|
12
|
+
* which approximates the bar. Three honesty rules follow:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Never a violation.** The finding is always `incomplete` — a person
|
|
15
|
+
* decides 3.1.5, this check only tells them where to look.
|
|
16
|
+
* 2. **Fire only above a margin.** Automated scoring cannot remove proper
|
|
17
|
+
* names, which skews grades HIGH, so the finding fires a full grade above
|
|
18
|
+
* the target (FK > 10 against a target of 9; LIX > 48 against 44). A page
|
|
19
|
+
* at 9.4 is not a finding; a page at 11 is worth a person's time.
|
|
20
|
+
* 3. **Language-gated.** Flesch–Kincaid is calibrated for English; LIX is
|
|
21
|
+
* the conventional measure for the Nordic languages (thresholds are
|
|
22
|
+
* convention, not W3C-blessed: <35 easy, 45+ difficult, "lower
|
|
23
|
+
* secondary" ≈ 44). This is the same routing Sa11y documents publicly —
|
|
24
|
+
* the behaviour, never its GPL code. An unsupported or missing `lang`
|
|
25
|
+
* skips silently: missing `lang` is already an axe violation (3.1.1),
|
|
26
|
+
* and scoring text in a language the formula was never calibrated for
|
|
27
|
+
* would manufacture a number.
|
|
28
|
+
*
|
|
29
|
+
* Extraction happens in-page (main content only — scoring navigation chrome
|
|
30
|
+
* would grade the menu, not the writing); scoring happens Node-side in the
|
|
31
|
+
* worker, which keeps text-readability out of the injected bundle and the
|
|
32
|
+
* page's text out of everything: only the scores and the language ever leave
|
|
33
|
+
* the worker.
|
|
34
|
+
*/
|
|
35
|
+
/** The one node the page-level finding points at. */
|
|
36
|
+
export const READING_LEVEL_RULE_ID = 'setsquare-reading-level';
|
|
37
|
+
/** Versioned for the ruleset digest: bump when scoring or thresholds change. */
|
|
38
|
+
export const READING_LEVEL_CHECK_VERSION = `${READING_LEVEL_RULE_ID}@1`;
|
|
39
|
+
const MIN_WORDS = 100;
|
|
40
|
+
const FLESCH_KINCAID_TARGET = 9;
|
|
41
|
+
const FLESCH_KINCAID_FIRE_ABOVE = 10;
|
|
42
|
+
const LIX_TARGET = 44;
|
|
43
|
+
const LIX_FIRE_ABOVE = 48;
|
|
44
|
+
/** Enough prose to grade a page; more only slows the formulas down. */
|
|
45
|
+
const MAX_CHARS = 20_000;
|
|
46
|
+
const FLESCH_LANGS = new Set(['en']);
|
|
47
|
+
const LIX_LANGS = new Set(['da', 'fi', 'nb', 'nn', 'no', 'sv']);
|
|
48
|
+
/**
|
|
49
|
+
* Main-content text and the document language, from the live page.
|
|
50
|
+
*
|
|
51
|
+
* `<main>`/`role=main` when the page declares one; otherwise the body minus
|
|
52
|
+
* nav/header/footer/aside, which is the same "grade the writing, not the
|
|
53
|
+
* chrome" heuristic every readability tool uses. Returns undefined on any
|
|
54
|
+
* failure — a page that cannot be read is a page that is not scored.
|
|
55
|
+
*/
|
|
56
|
+
export async function extractMainText(page) {
|
|
57
|
+
try {
|
|
58
|
+
return await page.evaluate((maxChars) => {
|
|
59
|
+
const lang = document.documentElement.lang.trim().toLowerCase();
|
|
60
|
+
if (!lang)
|
|
61
|
+
return undefined;
|
|
62
|
+
const main = document.querySelector('main, [role="main"]');
|
|
63
|
+
let text;
|
|
64
|
+
if (main instanceof HTMLElement) {
|
|
65
|
+
text = main.innerText;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const clone = document.body.cloneNode(true);
|
|
69
|
+
const chrome = clone.querySelectorAll('nav, header, footer, aside, script, style');
|
|
70
|
+
for (let i = 0; i < chrome.length; i += 1)
|
|
71
|
+
chrome[i]?.remove();
|
|
72
|
+
text = clone.innerText ?? clone.textContent ?? '';
|
|
73
|
+
}
|
|
74
|
+
return { lang, text: text.slice(0, maxChars) };
|
|
75
|
+
}, MAX_CHARS);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* LIX = words/sentences + 100 × longWords/words, long = more than 6 letters.
|
|
83
|
+
* Computed here rather than imported: the formula is four lines, and the only
|
|
84
|
+
* library candidates are unmaintained.
|
|
85
|
+
*/
|
|
86
|
+
function lix(text) {
|
|
87
|
+
const words = text.split(/\s+/).filter((word) => /\p{L}/u.test(word));
|
|
88
|
+
const sentences = text.split(/[.!?:;]+/).filter((part) => /\p{L}/u.test(part));
|
|
89
|
+
if (words.length === 0 || sentences.length === 0)
|
|
90
|
+
return { score: 0, words: 0 };
|
|
91
|
+
const longWords = words.filter((word) => word.replace(/\P{L}/gu, '').length > 6);
|
|
92
|
+
return {
|
|
93
|
+
score: words.length / sentences.length + (100 * longWords.length) / words.length,
|
|
94
|
+
words: words.length,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function scoreReadingLevel(extracted) {
|
|
98
|
+
const primary = extracted.lang.split('-')[0] ?? '';
|
|
99
|
+
if (FLESCH_LANGS.has(primary)) {
|
|
100
|
+
const words = extracted.text.split(/\s+/).filter((word) => /\p{L}/u.test(word)).length;
|
|
101
|
+
if (words < MIN_WORDS)
|
|
102
|
+
return undefined;
|
|
103
|
+
const score = readability.fleschKincaidGrade(extracted.text);
|
|
104
|
+
if (!Number.isFinite(score))
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
lang: extracted.lang,
|
|
108
|
+
formula: 'flesch-kincaid',
|
|
109
|
+
score: Math.round(score * 10) / 10,
|
|
110
|
+
target: FLESCH_KINCAID_TARGET,
|
|
111
|
+
words,
|
|
112
|
+
flagged: score > FLESCH_KINCAID_FIRE_ABOVE,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (LIX_LANGS.has(primary)) {
|
|
116
|
+
const { score, words } = lix(extracted.text);
|
|
117
|
+
if (words < MIN_WORDS)
|
|
118
|
+
return undefined;
|
|
119
|
+
return {
|
|
120
|
+
lang: extracted.lang,
|
|
121
|
+
formula: 'lix',
|
|
122
|
+
score: Math.round(score * 10) / 10,
|
|
123
|
+
target: LIX_TARGET,
|
|
124
|
+
words,
|
|
125
|
+
flagged: score > LIX_FIRE_ABOVE,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Unsupported language: silence, not a guess.
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The heuristic finding, only when the score clears the margin.
|
|
133
|
+
*
|
|
134
|
+
* Page-level, so `targets` names one node — the page — with no selectors. A
|
|
135
|
+
* `nodeCount` of 0 would make a surface failing only this check render as
|
|
136
|
+
* clean, which is the false pass this product exists to avoid.
|
|
137
|
+
*/
|
|
138
|
+
export function readingLevelFinding(score) {
|
|
139
|
+
if (!score.flagged)
|
|
140
|
+
return undefined;
|
|
141
|
+
const grade = score.formula === 'flesch-kincaid'
|
|
142
|
+
? `Estimated reading grade ${score.score} (target ≤ ${score.target} after removing proper names)`
|
|
143
|
+
: `LIX ${score.score} (target ≤ ${score.target}; conventional mapping, not W3C-defined)`;
|
|
144
|
+
return {
|
|
145
|
+
ruleId: READING_LEVEL_RULE_ID,
|
|
146
|
+
status: 'incomplete',
|
|
147
|
+
wcag: { criteria: ['3.1.5'], level: 'AAA', versionTags: [] },
|
|
148
|
+
targets: { nodeCount: 1, selectors: [], selectorsTruncated: false },
|
|
149
|
+
helpUrl: READING_LEVEL_HELP_URL,
|
|
150
|
+
evidence: {
|
|
151
|
+
failureSummary: `${grade}. 3.1.5 can also be met with supplemental content or a simpler ` +
|
|
152
|
+
'alternate version — needs a person.',
|
|
153
|
+
/*
|
|
154
|
+
* Through the same gate as axe's own data, even though every value here
|
|
155
|
+
* is ours. The invariant worth having is not "this rule is careful" but
|
|
156
|
+
* "nothing reaches `evidence.computed` without passing the contract's
|
|
157
|
+
* key list" — a rule that exempts itself is the hole that invariant is
|
|
158
|
+
* for. Today this is a no-op; the day someone adds the failing sentence
|
|
159
|
+
* to the payload, it stops being one.
|
|
160
|
+
*/
|
|
161
|
+
computed: sanitiseComputed({
|
|
162
|
+
formula: score.formula,
|
|
163
|
+
score: score.score,
|
|
164
|
+
target: score.target,
|
|
165
|
+
words: score.words,
|
|
166
|
+
lang: score.lang,
|
|
167
|
+
}, READING_LEVEL_RULE_ID),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The whole check: extract, gate, score, and return a finding or nothing.
|
|
173
|
+
* Never throws — a readability heuristic must not be able to cost a scan.
|
|
174
|
+
*/
|
|
175
|
+
export async function runReadingLevel(page) {
|
|
176
|
+
try {
|
|
177
|
+
const extracted = await extractMainText(page);
|
|
178
|
+
if (!extracted)
|
|
179
|
+
return undefined;
|
|
180
|
+
const score = scoreReadingLevel(extracted);
|
|
181
|
+
if (!score)
|
|
182
|
+
return undefined;
|
|
183
|
+
return readingLevelFinding(score);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=reading-level.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reading-level.js","sourceRoot":"","sources":["../src/reading-level.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAC/D,gFAAgF;AAChF,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,qBAAqB,IAAI,CAAC;AAExE,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAChC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AACrC,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,uEAAuE;AACvE,MAAM,SAAS,GAAG,MAAM,CAAC;AAEzB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAgBhE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAU;IAC9C,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,EAAE;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAChE,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAE5B,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;YAC3D,IAAI,IAAY,CAAC;YACjB,IAAI,IAAI,YAAY,WAAW,EAAE,CAAC;gBAChC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAgB,CAAC;gBAC3D,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC,2CAA2C,CAAC,CAAC;gBACnF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;oBAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC/D,IAAI,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC;YACpD,CAAC;YAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;QACjD,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,GAAG,CAAC,IAAY;IACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAEhF,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjF,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM;QAChF,KAAK,EAAE,KAAK,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAwB;IACxD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnD,IAAI,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;QACvF,IAAI,KAAK,GAAG,SAAS;YAAE,OAAO,SAAS,CAAC;QAExC,MAAM,KAAK,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAE9C,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,OAAO,EAAE,gBAAgB;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE;YAClC,MAAM,EAAE,qBAAqB;YAC7B,KAAK;YACL,OAAO,EAAE,KAAK,GAAG,yBAAyB;SAC3C,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,KAAK,GAAG,SAAS;YAAE,OAAO,SAAS,CAAC;QAExC,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE;YAClC,MAAM,EAAE,UAAU;YAClB,KAAK;YACL,OAAO,EAAE,KAAK,GAAG,cAAc;SAChC,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAwB;IAC1D,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAErC,MAAM,KAAK,GACT,KAAK,CAAC,OAAO,KAAK,gBAAgB;QAChC,CAAC,CAAC,2BAA2B,KAAK,CAAC,KAAK,cAAc,KAAK,CAAC,MAAM,+BAA+B;QACjG,CAAC,CAAC,OAAO,KAAK,CAAC,KAAK,cAAc,KAAK,CAAC,MAAM,0CAA0C,CAAC;IAE7F,OAAO;QACL,MAAM,EAAE,qBAAqB;QAC7B,MAAM,EAAE,YAAY;QACpB,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE;QAC5D,OAAO,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;QACnE,OAAO,EAAE,sBAAsB;QAC/B,QAAQ,EAAE;YACR,cAAc,EACZ,GAAG,KAAK,iEAAiE;gBACzE,qCAAqC;YACvC;;;;;;;eAOG;YACH,QAAQ,EAAE,gBAAgB,CACxB;gBACE,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,EACD,qBAAqB,CACtB;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAU;IAC9C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QAEjC,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult as PlaywrightTestResult } from '@playwright/test/reporter';
|
|
2
|
+
import { type SetsquareOptions } from './options.js';
|
|
3
|
+
/**
|
|
4
|
+
* The interpretation half of the SDK, and the only thing that exists once per
|
|
5
|
+
* run.
|
|
6
|
+
*
|
|
7
|
+
* Everything that is a policy lives here — envelope assembly, output, and (as
|
|
8
|
+
* later stages land) dedupe, precedence, surface normalisation and persona/theme
|
|
9
|
+
* resolution. The fixture observes in N workers and decides nothing; this decides
|
|
10
|
+
* everything and observes nothing. Splitting it any other way means two places
|
|
11
|
+
* implementing one policy, and they drift.
|
|
12
|
+
*
|
|
13
|
+
* Nothing in this file makes a network call, and nothing in this file can. That
|
|
14
|
+
* is not a configuration — there is no client, no URL, and no code path that
|
|
15
|
+
* would use one. "Zero network calls with no token" is asserted in the test suite
|
|
16
|
+
* against the real Node http stack, because a promise of restraint is not a
|
|
17
|
+
* property.
|
|
18
|
+
*/
|
|
19
|
+
export default class SetsquareReporter implements Reporter {
|
|
20
|
+
private readonly options;
|
|
21
|
+
private readonly outcomes;
|
|
22
|
+
private readonly warnings;
|
|
23
|
+
private startedAt;
|
|
24
|
+
private baseDir;
|
|
25
|
+
private infrastructure;
|
|
26
|
+
private reportPath;
|
|
27
|
+
private plannedTests;
|
|
28
|
+
/** Set only when the orchestrator declared a run group via SETSQUARE_RUN_ID. */
|
|
29
|
+
private group;
|
|
30
|
+
private groupDir;
|
|
31
|
+
private partId;
|
|
32
|
+
private startedAtIso;
|
|
33
|
+
/** The begun-marker write, started in onBegin and settled before the merge. */
|
|
34
|
+
private begunWrite;
|
|
35
|
+
/** Image attachments Playwright held in memory, which nothing can link to. */
|
|
36
|
+
private inMemoryShots;
|
|
37
|
+
private unmeasuredAxes;
|
|
38
|
+
private projectNames;
|
|
39
|
+
private configuredWorkers;
|
|
40
|
+
private configFile;
|
|
41
|
+
constructor(options?: SetsquareOptions);
|
|
42
|
+
/** Playwright reads this to decide whether stdout should be captured. */
|
|
43
|
+
printsToStdio(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* `playwright test --list` enumerates tests and runs none of them.
|
|
46
|
+
*
|
|
47
|
+
* Playwright still calls `onBegin` and `onEnd` for a listing, and the reporter
|
|
48
|
+
* used to treat that as a run: it wrote a `run.json` with zero results, printed a
|
|
49
|
+
* summary, and — because `pruneRuns` replaces older run directories, by the "no
|
|
50
|
+
* local history" design — **DELETED the previous real run's evidence.** Measured:
|
|
51
|
+
* a run of 14 scans, then one `--list`, and the 14-scan directory was gone,
|
|
52
|
+
* replaced by an empty one. `setsquare report` then pointed at an empty report.
|
|
53
|
+
*
|
|
54
|
+
* In a run group it was worse than data loss: the listing wrote a begun marker
|
|
55
|
+
* and never completed, so the group's census counted a part that never existed
|
|
56
|
+
* and the merged coverage read `partial` forever.
|
|
57
|
+
*
|
|
58
|
+
* Detected from argv rather than from "no tests ended", because a run that was
|
|
59
|
+
* interrupted before its first test looks identical from the Reporter API — and
|
|
60
|
+
* that IS a real run whose emptiness is worth recording. What the user typed is
|
|
61
|
+
* the only signal that separates the two.
|
|
62
|
+
*/
|
|
63
|
+
private readonly listOnly;
|
|
64
|
+
onBegin(config: FullConfig, suite: Suite): void;
|
|
65
|
+
onTestEnd(test: TestCase, result: PlaywrightTestResult): void;
|
|
66
|
+
onEnd(result?: FullResult): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* The grouped ending: write this invocation's part, then re-derive the
|
|
69
|
+
* merged logical run from every part present.
|
|
70
|
+
*
|
|
71
|
+
* The terminal keeps speaking for THIS invocation — a CI log per lane must
|
|
72
|
+
* describe its lane, and two complete-looking merged summaries in one
|
|
73
|
+
* pipeline log is how someone quotes the wrong one — with a group block
|
|
74
|
+
* naming the merged artifact and what is known about its coverage. The
|
|
75
|
+
* merged run.json / meta.json / report.html are the logical run.
|
|
76
|
+
*/
|
|
77
|
+
private endGrouped;
|
|
78
|
+
/**
|
|
79
|
+
* The same summary again, as markdown, when someone asked for a copy.
|
|
80
|
+
*
|
|
81
|
+
* Rendered from the SAME `SummaryInput` the terminal was given, which is the
|
|
82
|
+
* whole point: a CI job summary that disagreed with the log above it would be
|
|
83
|
+
* worse than no job summary at all. `format` is not consulted — this
|
|
84
|
+
* destination is markdown by definition, and a suite configured for `json`
|
|
85
|
+
* still wants a readable panel.
|
|
86
|
+
*
|
|
87
|
+
* Silent on failure, and deliberately so. This is the last thing the reporter
|
|
88
|
+
* does; a path the process cannot write must not swallow the summary that was
|
|
89
|
+
* already printed or the evidence that was already saved.
|
|
90
|
+
*/
|
|
91
|
+
private writeSummaryFile;
|
|
92
|
+
}
|
|
93
|
+
export declare function unmeasuredAxes(projects: FullConfig['projects']): string[];
|
|
94
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,UAAU,IAAI,oBAAoB,EACnC,MAAM,2BAA2B,CAAC;AAwBnC,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAY1F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,YAAY,CAAqB;IACzC,gFAAgF;IAChF,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAM;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAA4B;IAC9C,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,UAAU,CAAqB;gBAE3B,OAAO,GAAE,gBAAqB;IAI1C,yEAAyE;IACzE,aAAa,IAAI,OAAO;IAIxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAE5D,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAqG/C,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAiDvD,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAqJ/C;;;;;;;;;OASG;YACW,UAAU;IAiKxB;;;;;;;;;;;;OAYG;YACW,gBAAgB;CAU/B;AAwKD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,GAAG,MAAM,EAAE,CA8BzE"}
|