@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,334 @@
|
|
|
1
|
+
import type { AxeFinding, AxeScan, PageRuntime, Perf, ScanDocument, RuleInapplicable, RulePass, RunIngestRequest, UnscannedRow } from '@setsquare/schema';
|
|
2
|
+
import type { WcagTarget } from './rules.js';
|
|
3
|
+
import type { DeclaredAxes } from '@setsquare/schema/conformance';
|
|
4
|
+
import type { OverheadFacts, SuiteFacts } from './suite.js';
|
|
5
|
+
/**
|
|
6
|
+
* The fixture ↔ reporter contract.
|
|
7
|
+
*
|
|
8
|
+
* The seam is CAPTURE vs INTERPRETATION, and it is drawn here rather than
|
|
9
|
+
* anywhere convenient. The fixture runs in N worker processes and observes; the
|
|
10
|
+
* reporter runs once in the main process and decides. Everything that is a
|
|
11
|
+
* policy — dedupe, precedence, surface normalisation, persona and theme
|
|
12
|
+
* resolution, envelope assembly, output — belongs to the reporter, because a
|
|
13
|
+
* policy implemented in two places is a policy that will diverge.
|
|
14
|
+
*
|
|
15
|
+
* That is why a `ScanRecord` carries raw observations (`observed`) and not
|
|
16
|
+
* conclusions. The fixture can see things the reporter never can — the live DOM,
|
|
17
|
+
* a `data-theme` attribute mid-test, the order navigations actually happened in
|
|
18
|
+
* — so it records them faithfully and forms no opinion about what they mean.
|
|
19
|
+
*
|
|
20
|
+
* Sanitisation is the single exception to "the fixture decides nothing", and it
|
|
21
|
+
* is deliberate: page-derived strings must be capped and constrained BEFORE they
|
|
22
|
+
* cross the attachment boundary, because an attachment is also written into
|
|
23
|
+
* Playwright's own HTML report and trace. Letting an unbounded hostile selector
|
|
24
|
+
* out of the worker and cleaning it up later would mean it had already been
|
|
25
|
+
* written somewhere we do not control. Sanitisation is a pure function, so it
|
|
26
|
+
* carries no state across the seam.
|
|
27
|
+
*/
|
|
28
|
+
/** Attachment name. Namespaced so it cannot collide with a customer's own. */
|
|
29
|
+
export declare const SCAN_RECORD_ATTACHMENT = "setsquare.scan-record";
|
|
30
|
+
/**
|
|
31
|
+
* Bumped when the record shape changes incompatibly.
|
|
32
|
+
*
|
|
33
|
+
* Fixture and reporter are always the same install, so this is not a
|
|
34
|
+
* compatibility mechanism in the wire-format sense — it is a loud failure when
|
|
35
|
+
* a stale build lingers in a customer's `node_modules` after an upgrade, which
|
|
36
|
+
* would otherwise present as mysteriously missing evidence.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SCAN_RECORD_VERSION = 1;
|
|
39
|
+
/** A single main-frame navigation, as the fixture saw it happen. */
|
|
40
|
+
export interface ObservedNavigation {
|
|
41
|
+
url: string;
|
|
42
|
+
/** Milliseconds since the context was created. Relative, so no clock is trusted. */
|
|
43
|
+
atMs: number;
|
|
44
|
+
/**
|
|
45
|
+
* How the URL came to change. `pushState`/`popstate` never fire a real
|
|
46
|
+
* navigation, and a trigger that listens only for `framenavigated` misses
|
|
47
|
+
* every SPA route change — which is most of a modern app.
|
|
48
|
+
*/
|
|
49
|
+
kind: 'navigation' | 'pushState' | 'replaceState' | 'popstate';
|
|
50
|
+
}
|
|
51
|
+
/** What the fixture saw, with no interpretation applied. */
|
|
52
|
+
export interface ObservedContext {
|
|
53
|
+
/** Playwright project name. The reporter maps this to persona/theme; the fixture does not. */
|
|
54
|
+
project: string;
|
|
55
|
+
engine: 'chromium' | 'firefox' | 'webkit';
|
|
56
|
+
/** Every main-frame URL this page held, in order. */
|
|
57
|
+
navigations: ObservedNavigation[];
|
|
58
|
+
viewport?: {
|
|
59
|
+
width: number;
|
|
60
|
+
height: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* `data-theme` (and friends) as read from the live document. The reporter
|
|
64
|
+
* ranks this BELOW an explicit config statement — an in-test toggle is
|
|
65
|
+
* truthful, but a customer telling us outranks us sniffing.
|
|
66
|
+
*/
|
|
67
|
+
observedTheme?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* What the fixture saw a page do, attached at teardown.
|
|
71
|
+
*
|
|
72
|
+
* Per page rather than per test because a single test may open several — the
|
|
73
|
+
* reference implementation drives two sessions in one spec — and collapsing them
|
|
74
|
+
* would attribute one page's evidence to another's context.
|
|
75
|
+
*/
|
|
76
|
+
export interface ObservationRecord {
|
|
77
|
+
recordVersion: typeof SCAN_RECORD_VERSION;
|
|
78
|
+
kind: 'observation';
|
|
79
|
+
observed: ObservedContext;
|
|
80
|
+
/**
|
|
81
|
+
* Surfaces this page reached and could not scan, aggregated by reason.
|
|
82
|
+
*
|
|
83
|
+
* Absent means this page recorded no ledger events — NOT that nothing went
|
|
84
|
+
* unscanned, which is what an empty array would claim. The reporter sums these
|
|
85
|
+
* across records, which it must do for run groups anyway.
|
|
86
|
+
*/
|
|
87
|
+
unscanned?: UnscannedRow[];
|
|
88
|
+
/** Events dropped past the ledger's distinct-row cap. Absent means none were. */
|
|
89
|
+
unscannedOmitted?: number;
|
|
90
|
+
}
|
|
91
|
+
/** Where a scan happened, as the page reported it at that moment. */
|
|
92
|
+
export interface ScanPageFacts {
|
|
93
|
+
project: string;
|
|
94
|
+
engine: ObservedContext['engine'];
|
|
95
|
+
url: string;
|
|
96
|
+
viewport?: {
|
|
97
|
+
width: number;
|
|
98
|
+
height: number;
|
|
99
|
+
};
|
|
100
|
+
observedTheme?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* What the customer said about a scan they asked for.
|
|
104
|
+
*
|
|
105
|
+
* Separate from what we observed, and deliberately so: an explicit statement
|
|
106
|
+
* outranks inference everywhere, and keeping the two apart is what lets the
|
|
107
|
+
* reporter apply that precedence instead of guessing which is which.
|
|
108
|
+
*/
|
|
109
|
+
export interface DeclaredContext {
|
|
110
|
+
surface: string;
|
|
111
|
+
state?: string;
|
|
112
|
+
theme?: string;
|
|
113
|
+
persona?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The result of one `setsquare.scan()` call — attached from the test body rather than
|
|
117
|
+
* from teardown, because that is where the customer asked for it.
|
|
118
|
+
*
|
|
119
|
+
* Carries its own `axeVersion` and `rulesetDigest`. Per scan, not per run: a
|
|
120
|
+
* sharded run can in principle be produced by runners with drifted dependencies,
|
|
121
|
+
* and a finding whose ruleset we cannot name is a finding we cannot defend.
|
|
122
|
+
*/
|
|
123
|
+
export interface ScanResultRecord {
|
|
124
|
+
recordVersion: typeof SCAN_RECORD_VERSION;
|
|
125
|
+
kind: 'scan';
|
|
126
|
+
page: ScanPageFacts;
|
|
127
|
+
declared: DeclaredContext;
|
|
128
|
+
/** Groups scans of one page load, so perf is not counted once per state. */
|
|
129
|
+
visitRef: string;
|
|
130
|
+
axeVersion: string;
|
|
131
|
+
rulesetDigest: string;
|
|
132
|
+
/** The conformance level assessed. Stated, never inferred from what was found. */
|
|
133
|
+
wcagTarget: WcagTarget;
|
|
134
|
+
findings: AxeFinding[];
|
|
135
|
+
passes: RulePass[];
|
|
136
|
+
/** Rules that ran and found nothing to check — EARL's inapplicable. */
|
|
137
|
+
inapplicable?: RuleInapplicable[];
|
|
138
|
+
/** Present on the FIRST scan of a page load only. Absent is valid and common. */
|
|
139
|
+
perf?: Perf;
|
|
140
|
+
/**
|
|
141
|
+
* What the page did while we were on it. First scan of a page load only, for
|
|
142
|
+
* the same reason `perf` is: these are facts about a LOAD, not about a scan.
|
|
143
|
+
*/
|
|
144
|
+
runtime?: PageRuntime;
|
|
145
|
+
/** Document-level values for cross-surface consistency. */
|
|
146
|
+
document?: ScanDocument;
|
|
147
|
+
/**
|
|
148
|
+
* What this scan cost us, in milliseconds.
|
|
149
|
+
*
|
|
150
|
+
* Local-only — it never reaches the wire, because it is a measurement about US
|
|
151
|
+
* rather than about the customer's application, and the next run reproduces it.
|
|
152
|
+
* It exists so a run can state its own overhead instead of the customer having
|
|
153
|
+
* to wonder, which is a trust signal and pre-empts the "your SDK slowed our CI"
|
|
154
|
+
* story.
|
|
155
|
+
*
|
|
156
|
+
* Time SPENT, never time ADDED: these are summed across workers and scanning is
|
|
157
|
+
* concurrent with the customer's own tests, so the total is not wall clock added
|
|
158
|
+
* to the run. Nothing may render it as a delta or a percentage — that needs a
|
|
159
|
+
* control run, which is what `SETSQUARE_DISABLED` is for.
|
|
160
|
+
*/
|
|
161
|
+
cost?: {
|
|
162
|
+
totalMs: number;
|
|
163
|
+
axeMs: number;
|
|
164
|
+
probesMs?: number;
|
|
165
|
+
};
|
|
166
|
+
/** How the page was judged ready. Absent on explicit scans — no heuristic ran. */
|
|
167
|
+
settle?: AxeScan['settle'];
|
|
168
|
+
}
|
|
169
|
+
export type ScanRecord = ObservationRecord | ScanResultRecord;
|
|
170
|
+
/**
|
|
171
|
+
* The disk envelope's sidecar.
|
|
172
|
+
*
|
|
173
|
+
* Local-only facts live here and NOWHERE in `run.json`, which is the ingest
|
|
174
|
+
* request body byte-for-byte. That separation is what lets the SDK's own test
|
|
175
|
+
* suite validate the artifact against the generated JSON Schema and mean it: the
|
|
176
|
+
* file we write is the file the platform would have received, so "minus nothing"
|
|
177
|
+
* is a checkable claim rather than a description.
|
|
178
|
+
*/
|
|
179
|
+
/**
|
|
180
|
+
* What the merge can honestly say about a run group's coverage.
|
|
181
|
+
*
|
|
182
|
+
* `complete` is only claimable against a DECLARED census (`SETSQUARE_RUN_PARTS`):
|
|
183
|
+
* without one, an invocation that crashed before it ever announced itself is
|
|
184
|
+
* invisible, and "no invocation known to be in flight" is not "complete".
|
|
185
|
+
* `unknown` is that sentence said out loud. `partial` means evidence is known
|
|
186
|
+
* to be absent — absent, never clean.
|
|
187
|
+
*/
|
|
188
|
+
export type GroupCoverage = 'complete' | 'partial' | 'unknown';
|
|
189
|
+
/** One invocation's contribution, as the merged sidecar accounts for it. */
|
|
190
|
+
export interface RunGroupPartMeta {
|
|
191
|
+
partId: string;
|
|
192
|
+
lane?: string;
|
|
193
|
+
/** The label used to namespace this part's testIds in the merged envelope. */
|
|
194
|
+
label: string;
|
|
195
|
+
startedAt: string;
|
|
196
|
+
generatedAt: string;
|
|
197
|
+
durationMs: number;
|
|
198
|
+
/** Playwright's own verdict for the invocation. `interrupted` taints the merge. */
|
|
199
|
+
playwrightStatus: 'passed' | 'failed' | 'timedout' | 'interrupted' | 'unknown';
|
|
200
|
+
tests: number;
|
|
201
|
+
scans: number;
|
|
202
|
+
plannedTests?: number;
|
|
203
|
+
/** That invocation's own Playwright HTML report, when it made one. */
|
|
204
|
+
reportPath?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The group accounting block of a merged run's sidecar.
|
|
208
|
+
*
|
|
209
|
+
* Local-only, like everything else in the sidecar. The envelope stays the pure
|
|
210
|
+
* wire body; which invocations produced it is provenance, and provenance lives
|
|
211
|
+
* here.
|
|
212
|
+
*/
|
|
213
|
+
export interface RunGroupMeta {
|
|
214
|
+
/** The orchestrator's id, after filesystem sanitisation. */
|
|
215
|
+
id: string;
|
|
216
|
+
/** The declared invocation count, or null when none was declared. */
|
|
217
|
+
expectedParts: number | null;
|
|
218
|
+
/** Invocations that announced themselves (wrote a begun marker). */
|
|
219
|
+
begun: number;
|
|
220
|
+
/** Invocations whose results made it into this merge. */
|
|
221
|
+
completed: number;
|
|
222
|
+
/** partIds that began but have no readable part — crashed, still running, or torn. */
|
|
223
|
+
missing: string[];
|
|
224
|
+
/** partIds excluded because they are hours older than the newest part. */
|
|
225
|
+
stale: string[];
|
|
226
|
+
coverage: GroupCoverage;
|
|
227
|
+
/** Wall-clock span from the earliest start to the latest finish across parts. */
|
|
228
|
+
spanMs: number;
|
|
229
|
+
/** Content hash of the run.json this sidecar describes. A mismatch means a torn pair. */
|
|
230
|
+
runJsonSha256: string;
|
|
231
|
+
parts: RunGroupPartMeta[];
|
|
232
|
+
}
|
|
233
|
+
export interface RunMeta {
|
|
234
|
+
/** Never a run id. The server assigns those, and no server was involved (P1-17.7). */
|
|
235
|
+
runId: null;
|
|
236
|
+
/** Said out loud, because a JSON file on disk has no other way to say it. */
|
|
237
|
+
submitted: false;
|
|
238
|
+
sdkVersion: string;
|
|
239
|
+
generatedAt: string;
|
|
240
|
+
outputDir: string;
|
|
241
|
+
envelopePath: string;
|
|
242
|
+
durationMs: number;
|
|
243
|
+
counts: {
|
|
244
|
+
tests: number;
|
|
245
|
+
pages: number;
|
|
246
|
+
scans: number;
|
|
247
|
+
/**
|
|
248
|
+
* How many tests the run intended to execute, when Playwright told us.
|
|
249
|
+
*
|
|
250
|
+
* Local-only for now, in the sidecar rather than the envelope. "82 of 112
|
|
251
|
+
* tests never ran" is genuinely evidence-relevant and will want a home on the
|
|
252
|
+
* wire eventually — but that is a schema change with a both-runtimes bar
|
|
253
|
+
* behind it, and the local path can be honest today without one.
|
|
254
|
+
*/
|
|
255
|
+
plannedTests?: number;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* The Playwright config this run came from, relative to `outputDir`'s base.
|
|
259
|
+
*
|
|
260
|
+
* Local-only, and it exists so a repro command can name the right `-c`. A
|
|
261
|
+
* monorepo with several configs writes several `.setsquare` directories, and a
|
|
262
|
+
* consumer reading one of them otherwise has no way to know which config
|
|
263
|
+
* produced it — which makes every derived `playwright test` invocation a guess.
|
|
264
|
+
*
|
|
265
|
+
* Absent when Playwright gave us no `configFile` (an inline config), in which
|
|
266
|
+
* case the honest output omits `-c` rather than inventing a path.
|
|
267
|
+
*/
|
|
268
|
+
configFile?: string;
|
|
269
|
+
/**
|
|
270
|
+
* What the run cost us, and what it spent that cost on.
|
|
271
|
+
*
|
|
272
|
+
* **Time SPENT, never time ADDED.** These are summed across workers while
|
|
273
|
+
* scanning runs concurrently with the customer's own tests, so the total is not
|
|
274
|
+
* wall clock added to the suite. Nothing may render a delta or a percentage from
|
|
275
|
+
* it: a delta needs a control run, which is what `SETSQUARE_DISABLED=1` exists
|
|
276
|
+
* to provide. Printing an unmeasurable delta would be the exact sin `perf.ts`
|
|
277
|
+
* was written to prevent.
|
|
278
|
+
*/
|
|
279
|
+
overhead?: OverheadFacts;
|
|
280
|
+
/**
|
|
281
|
+
* Suite-shaped facts from Playwright's own result object.
|
|
282
|
+
*
|
|
283
|
+
* Local-only because these describe the RUN's mechanics rather than the
|
|
284
|
+
* customer's application. `workerIndex` and `failure` do reach the wire, because
|
|
285
|
+
* they are per-test evidence a platform would trend; these aggregates are
|
|
286
|
+
* derivable from them and are precomputed only so the three renderers cannot
|
|
287
|
+
* disagree.
|
|
288
|
+
*/
|
|
289
|
+
suite?: SuiteFacts;
|
|
290
|
+
/**
|
|
291
|
+
* Which axes this run's evidence actually spans, and which it never touched.
|
|
292
|
+
*
|
|
293
|
+
* `declaredAxes` comes from the customer's own theme/persona maps and project
|
|
294
|
+
* list, and is the ONLY real denominator available on any axis — "4 personas
|
|
295
|
+
* configured, 1 with evidence" is defensible where anything derived from
|
|
296
|
+
* observed values alone is not. Absent means we could not read the config, and
|
|
297
|
+
* renderers must then suppress the line rather than reporting N = M.
|
|
298
|
+
*
|
|
299
|
+
* `unmeasuredAxes` names Playwright `use` options that would create an evidence
|
|
300
|
+
* axis and were not set by any project — `forcedColors`, `reducedMotion`,
|
|
301
|
+
* `colorScheme`. Saying "those axes are unmeasured in this run" is true, costs
|
|
302
|
+
* nothing, and is a great deal more honest than the alternative of quietly
|
|
303
|
+
* scanning one setting and calling it coverage.
|
|
304
|
+
*/
|
|
305
|
+
coverage?: {
|
|
306
|
+
/**
|
|
307
|
+
* Keyed by AXIS NAME (`theme`, `persona`, `state`), matching
|
|
308
|
+
* `conformance.ts`'s own vocabulary so the value can be handed straight to
|
|
309
|
+
* `axisCoverage` without translation. An earlier draft used plural nouns and
|
|
310
|
+
* had to be reconciled — two shapes for one concept is precisely the drift
|
|
311
|
+
* this codebase keeps single sources of truth to avoid.
|
|
312
|
+
*/
|
|
313
|
+
declaredAxes?: DeclaredAxes;
|
|
314
|
+
/**
|
|
315
|
+
* Playwright project names.
|
|
316
|
+
*
|
|
317
|
+
* Kept apart from `declaredAxes` because a project is NOT an axis — it is the
|
|
318
|
+
* provenance a theme or persona is resolved FROM. Filing it under axes would
|
|
319
|
+
* make "3 of 5 projects have evidence" look like a coverage figure on the
|
|
320
|
+
* grid, which it is not.
|
|
321
|
+
*/
|
|
322
|
+
declaredProjects?: string[];
|
|
323
|
+
unmeasuredAxes: string[];
|
|
324
|
+
};
|
|
325
|
+
/** Anything the SDK could not determine, named rather than guessed. */
|
|
326
|
+
unknowns: string[];
|
|
327
|
+
/** Warnings raised during the run. The SDK never fails a build; it says so here. */
|
|
328
|
+
warnings: string[];
|
|
329
|
+
/** Present only on merged run-group artifacts. See `RunGroupMeta`. */
|
|
330
|
+
group?: RunGroupMeta;
|
|
331
|
+
}
|
|
332
|
+
/** Convenience alias — the wire body, exactly as `packages/schema` defines it. */
|
|
333
|
+
export type RunEnvelope = RunIngestRequest;
|
|
334
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixture ↔ reporter contract.
|
|
3
|
+
*
|
|
4
|
+
* The seam is CAPTURE vs INTERPRETATION, and it is drawn here rather than
|
|
5
|
+
* anywhere convenient. The fixture runs in N worker processes and observes; the
|
|
6
|
+
* reporter runs once in the main process and decides. Everything that is a
|
|
7
|
+
* policy — dedupe, precedence, surface normalisation, persona and theme
|
|
8
|
+
* resolution, envelope assembly, output — belongs to the reporter, because a
|
|
9
|
+
* policy implemented in two places is a policy that will diverge.
|
|
10
|
+
*
|
|
11
|
+
* That is why a `ScanRecord` carries raw observations (`observed`) and not
|
|
12
|
+
* conclusions. The fixture can see things the reporter never can — the live DOM,
|
|
13
|
+
* a `data-theme` attribute mid-test, the order navigations actually happened in
|
|
14
|
+
* — so it records them faithfully and forms no opinion about what they mean.
|
|
15
|
+
*
|
|
16
|
+
* Sanitisation is the single exception to "the fixture decides nothing", and it
|
|
17
|
+
* is deliberate: page-derived strings must be capped and constrained BEFORE they
|
|
18
|
+
* cross the attachment boundary, because an attachment is also written into
|
|
19
|
+
* Playwright's own HTML report and trace. Letting an unbounded hostile selector
|
|
20
|
+
* out of the worker and cleaning it up later would mean it had already been
|
|
21
|
+
* written somewhere we do not control. Sanitisation is a pure function, so it
|
|
22
|
+
* carries no state across the seam.
|
|
23
|
+
*/
|
|
24
|
+
/** Attachment name. Namespaced so it cannot collide with a customer's own. */
|
|
25
|
+
export const SCAN_RECORD_ATTACHMENT = 'setsquare.scan-record';
|
|
26
|
+
/**
|
|
27
|
+
* Bumped when the record shape changes incompatibly.
|
|
28
|
+
*
|
|
29
|
+
* Fixture and reporter are always the same install, so this is not a
|
|
30
|
+
* compatibility mechanism in the wire-format sense — it is a loud failure when
|
|
31
|
+
* a stale build lingers in a customer's `node_modules` after an upgrade, which
|
|
32
|
+
* would otherwise present as mysteriously missing evidence.
|
|
33
|
+
*/
|
|
34
|
+
export const SCAN_RECORD_VERSION = 1;
|
|
35
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC"}
|
package/dist/dedupe.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AxeScan } from '@setsquare/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Which scan survives when several describe the same thing.
|
|
4
|
+
*
|
|
5
|
+
* This file exists because of one observation about the reference
|
|
6
|
+
* implementation, made before any code was written: **Eos's `warmup` project
|
|
7
|
+
* visits every entry route in the application first**, purely to pay
|
|
8
|
+
* cold-compile costs. Under the dedupe the phase plan originally specified —
|
|
9
|
+
* keyed on URL, first-wins — every surface's evidence in the run would have come
|
|
10
|
+
* from that shallow warm-up pass, and every real spec's scan would have been
|
|
11
|
+
* discarded. Not an edge case: the default outcome on the target we chose.
|
|
12
|
+
*
|
|
13
|
+
* So the rule is keyed on the full context tuple, and it is BEST-wins rather than
|
|
14
|
+
* first-wins.
|
|
15
|
+
*/
|
|
16
|
+
export interface DedupeInput {
|
|
17
|
+
scan: AxeScan;
|
|
18
|
+
/** The Playwright project that produced it. */
|
|
19
|
+
project: string;
|
|
20
|
+
/** True when the project exists to prepare the suite rather than to test it. */
|
|
21
|
+
infrastructure: boolean;
|
|
22
|
+
/** Order encountered, used only to break ties deterministically. */
|
|
23
|
+
index: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Identity of a scan for dedupe purposes: WHERE it looked, in WHAT context.
|
|
27
|
+
*
|
|
28
|
+
* The full tuple, not the URL alone. Two scans of one surface under different
|
|
29
|
+
* themes are different evidence — collapsing them reports a themed surface as
|
|
30
|
+
* clean when only one of its themes was — and the same is true of state and
|
|
31
|
+
* persona.
|
|
32
|
+
*
|
|
33
|
+
* The *where* is the ROUTE PATTERN, not the surface name, and that distinction
|
|
34
|
+
* was bought with a bug. The surface name is something we choose: an explicit
|
|
35
|
+
* `setsquare.scan('hostile-page')` and the auto-scan of the very same URL produced
|
|
36
|
+
* `hostile-page` and `hostile`, two different strings, so both survived and every
|
|
37
|
+
* count in the run was doubled. Keying on the name lets our own naming decide
|
|
38
|
+
* what counts as the same page — and it gets it wrong precisely when a customer
|
|
39
|
+
* has told us something, which is the one case that must work.
|
|
40
|
+
*
|
|
41
|
+
* The route pattern is the page's identity as the application defines it, and
|
|
42
|
+
* parameterised routes are normalised into it already (`/items/42/edit` →
|
|
43
|
+
* `/items/:id/edit`), so it collapses exactly what should collapse. The surface
|
|
44
|
+
* name then comes from whichever scan WINS, which is how an explicit label ends
|
|
45
|
+
* up on the row instead of competing with an inferred one for a place in the list.
|
|
46
|
+
*/
|
|
47
|
+
export declare function contextKey(scan: AxeScan): string;
|
|
48
|
+
/**
|
|
49
|
+
* Collapse scans to one per context, keeping the best.
|
|
50
|
+
*
|
|
51
|
+
* Ties are broken by LAST-encountered, which is just as deterministic as first
|
|
52
|
+
* and materially more correct: a later scan of the same context has had longer to
|
|
53
|
+
* settle. Output order still follows first encounter, so two runs of the same
|
|
54
|
+
* suite produce byte-identical, diffable envelopes.
|
|
55
|
+
*/
|
|
56
|
+
export declare function dedupeScans(inputs: DedupeInput[]): {
|
|
57
|
+
kept: AxeScan[];
|
|
58
|
+
dropped: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Projects that exist to prepare the suite rather than to test it.
|
|
62
|
+
*
|
|
63
|
+
* Derived from the customer's OWN dependency graph rather than from a
|
|
64
|
+
* configuration list they would have to remember to write: in Playwright, a
|
|
65
|
+
* project named in another project's `dependencies` is by definition setup for
|
|
66
|
+
* it. On the reference implementation this identifies `preflight`, `setup`,
|
|
67
|
+
* `auth` and `warmup` with no configuration at all — which is the difference
|
|
68
|
+
* between a rule that works on the first run and one that works after someone
|
|
69
|
+
* reads the docs.
|
|
70
|
+
*
|
|
71
|
+
* A suite where every project depends on every other would mark them all as
|
|
72
|
+
* infrastructure; that is degenerate enough not to be worth guarding, and the
|
|
73
|
+
* effect would only be to make the scores uniform again.
|
|
74
|
+
*/
|
|
75
|
+
export declare function infrastructureProjects(projects: readonly {
|
|
76
|
+
name: string;
|
|
77
|
+
dependencies?: readonly string[];
|
|
78
|
+
}[]): Set<string>;
|
|
79
|
+
//# sourceMappingURL=dedupe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGhD;AA6BD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoBvF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,GACtE,GAAG,CAAC,MAAM,CAAC,CAQb"}
|
package/dist/dedupe.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity of a scan for dedupe purposes: WHERE it looked, in WHAT context.
|
|
3
|
+
*
|
|
4
|
+
* The full tuple, not the URL alone. Two scans of one surface under different
|
|
5
|
+
* themes are different evidence — collapsing them reports a themed surface as
|
|
6
|
+
* clean when only one of its themes was — and the same is true of state and
|
|
7
|
+
* persona.
|
|
8
|
+
*
|
|
9
|
+
* The *where* is the ROUTE PATTERN, not the surface name, and that distinction
|
|
10
|
+
* was bought with a bug. The surface name is something we choose: an explicit
|
|
11
|
+
* `setsquare.scan('hostile-page')` and the auto-scan of the very same URL produced
|
|
12
|
+
* `hostile-page` and `hostile`, two different strings, so both survived and every
|
|
13
|
+
* count in the run was doubled. Keying on the name lets our own naming decide
|
|
14
|
+
* what counts as the same page — and it gets it wrong precisely when a customer
|
|
15
|
+
* has told us something, which is the one case that must work.
|
|
16
|
+
*
|
|
17
|
+
* The route pattern is the page's identity as the application defines it, and
|
|
18
|
+
* parameterised routes are normalised into it already (`/items/42/edit` →
|
|
19
|
+
* `/items/:id/edit`), so it collapses exactly what should collapse. The surface
|
|
20
|
+
* name then comes from whichever scan WINS, which is how an explicit label ends
|
|
21
|
+
* up on the row instead of competing with an inferred one for a place in the list.
|
|
22
|
+
*/
|
|
23
|
+
export function contextKey(scan) {
|
|
24
|
+
const { surface, urlPattern, theme, persona, state } = scan.context;
|
|
25
|
+
return [urlPattern ?? surface, theme ?? '', persona ?? '', state ?? ''].join('\u0000');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How good a scan is as evidence for its context, higher being better.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT "how many findings it has". Ranking by finding count would
|
|
31
|
+
* make the noisiest scan win, which rewards scanning a page mid-render — the
|
|
32
|
+
* exact failure mode the settle heuristic exists to prevent.
|
|
33
|
+
*/
|
|
34
|
+
function score(input) {
|
|
35
|
+
let value = 0;
|
|
36
|
+
// A customer who called `setsquare.scan('job-editor', { state: … })` knows something
|
|
37
|
+
// we could never infer. It outranks anything automatic.
|
|
38
|
+
if (input.scan.context.surfaceSource === 'explicit')
|
|
39
|
+
value += 1000;
|
|
40
|
+
// A spec that tests the surface beats a project that merely warmed it.
|
|
41
|
+
if (!input.infrastructure)
|
|
42
|
+
value += 100;
|
|
43
|
+
// A scan that reached the page quietly beats one the ceiling cut short.
|
|
44
|
+
if (!input.scan.settle?.settleTimedOut)
|
|
45
|
+
value += 10;
|
|
46
|
+
// Richer context is more useful evidence, all else equal.
|
|
47
|
+
if (input.scan.context.persona)
|
|
48
|
+
value += 1;
|
|
49
|
+
if (input.scan.context.theme)
|
|
50
|
+
value += 1;
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Collapse scans to one per context, keeping the best.
|
|
55
|
+
*
|
|
56
|
+
* Ties are broken by LAST-encountered, which is just as deterministic as first
|
|
57
|
+
* and materially more correct: a later scan of the same context has had longer to
|
|
58
|
+
* settle. Output order still follows first encounter, so two runs of the same
|
|
59
|
+
* suite produce byte-identical, diffable envelopes.
|
|
60
|
+
*/
|
|
61
|
+
export function dedupeScans(inputs) {
|
|
62
|
+
const best = new Map();
|
|
63
|
+
for (const input of inputs) {
|
|
64
|
+
const key = contextKey(input.scan);
|
|
65
|
+
const incumbent = best.get(key);
|
|
66
|
+
// >= rather than >: on equal evidence the LATER scan wins.
|
|
67
|
+
//
|
|
68
|
+
// Later means more settled. A page whose content arrives after its first
|
|
69
|
+
// quiet window gets scanned twice — once as a shell, once complete — and both
|
|
70
|
+
// scans score identically, so a first-wins tie-break would enshrine the shell
|
|
71
|
+
// and report a clean page that is not. Measured on the fixture site: /slow
|
|
72
|
+
// reported zero violations until this line changed direction.
|
|
73
|
+
if (!incumbent || score(input) >= score(incumbent))
|
|
74
|
+
best.set(key, input);
|
|
75
|
+
}
|
|
76
|
+
const kept = [...best.values()].sort((a, b) => a.index - b.index).map((input) => input.scan);
|
|
77
|
+
return { kept, dropped: inputs.length - kept.length };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Projects that exist to prepare the suite rather than to test it.
|
|
81
|
+
*
|
|
82
|
+
* Derived from the customer's OWN dependency graph rather than from a
|
|
83
|
+
* configuration list they would have to remember to write: in Playwright, a
|
|
84
|
+
* project named in another project's `dependencies` is by definition setup for
|
|
85
|
+
* it. On the reference implementation this identifies `preflight`, `setup`,
|
|
86
|
+
* `auth` and `warmup` with no configuration at all — which is the difference
|
|
87
|
+
* between a rule that works on the first run and one that works after someone
|
|
88
|
+
* reads the docs.
|
|
89
|
+
*
|
|
90
|
+
* A suite where every project depends on every other would mark them all as
|
|
91
|
+
* infrastructure; that is degenerate enough not to be worth guarding, and the
|
|
92
|
+
* effect would only be to make the scores uniform again.
|
|
93
|
+
*/
|
|
94
|
+
export function infrastructureProjects(projects) {
|
|
95
|
+
const infrastructure = new Set();
|
|
96
|
+
for (const project of projects) {
|
|
97
|
+
for (const dependency of project.dependencies ?? [])
|
|
98
|
+
infrastructure.add(dependency);
|
|
99
|
+
}
|
|
100
|
+
return infrastructure;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;IACpE,OAAO,CAAC,UAAU,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,OAAO,IAAI,EAAE,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,KAAkB;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,qFAAqF;IACrF,wDAAwD;IACxD,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,KAAK,UAAU;QAAE,KAAK,IAAI,IAAI,CAAC;IAEnE,uEAAuE;IACvE,IAAI,CAAC,KAAK,CAAC,cAAc;QAAE,KAAK,IAAI,GAAG,CAAC;IAExC,wEAAwE;IACxE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc;QAAE,KAAK,IAAI,EAAE,CAAC;IAEpD,0DAA0D;IAC1D,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,KAAK,IAAI,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,KAAK,IAAI,CAAC,CAAC;IAEzC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,MAAqB;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEhC,2DAA2D;QAC3D,EAAE;QACF,yEAAyE;QACzE,8EAA8E;QAC9E,8EAA8E;QAC9E,2EAA2E;QAC3E,8DAA8D;QAC9D,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7F,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAuE;IAEvE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IAEzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,YAAY,IAAI,EAAE;YAAE,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import type { ScanDocument } from '@setsquare/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Document-level facts, read in one round-trip alongside the theme.
|
|
5
|
+
*
|
|
6
|
+
* The envelope has always carried rule OUTCOMES — `document-title`,
|
|
7
|
+
* `html-has-lang`, `bypass` — and never the VALUES, which is why no existing
|
|
8
|
+
* artefact can say "these three surfaces all claim to be Dashboard". On its own
|
|
9
|
+
* each of those pages has a perfectly good title; the finding only exists across
|
|
10
|
+
* surfaces, which is the class of thing only a multi-surface run can see.
|
|
11
|
+
*
|
|
12
|
+
* Folded into the theme read rather than added as a second `page.evaluate`: one
|
|
13
|
+
* protocol round-trip per scan instead of two, on a path that already runs for
|
|
14
|
+
* every scan in the suite.
|
|
15
|
+
*/
|
|
16
|
+
export interface DocumentFacts {
|
|
17
|
+
theme?: string;
|
|
18
|
+
document?: ScanDocument;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read theme and document facts from the live page.
|
|
22
|
+
*
|
|
23
|
+
* Returns `{}` on any failure. Every field is independently optional, and absence
|
|
24
|
+
* always means "not captured", never a negative finding — `documentConsistency`
|
|
25
|
+
* relies on that to keep an uncaptured surface out of `skipLinkAbsent`, which
|
|
26
|
+
* would otherwise manufacture findings out of our own gaps.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readDocumentFacts(page: Page): Promise<DocumentFacts>;
|
|
29
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AA6BD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAwE1E"}
|