@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,25 @@
|
|
|
1
|
+
import type { BrowserContext, TestInfo } from '@playwright/test';
|
|
2
|
+
import { type ResolvedSetsquareOptions } from './options.js';
|
|
3
|
+
export declare function installAutoScan(context: BrowserContext, options: ResolvedSetsquareOptions, testInfo: TestInfo, warn: (message: string) => void): Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Scan whatever the test left on screen, at teardown.
|
|
6
|
+
*
|
|
7
|
+
* This exists because of a measurement, and it is the single most important
|
|
8
|
+
* thing Stage 5 learned. Auto-scan fires 500ms after a page goes quiet — but a
|
|
9
|
+
* great many real tests finish in less than that. On the fixture suite, only two
|
|
10
|
+
* of ten surfaces were ever scanned: every fast test tore its context down before
|
|
11
|
+
* the settle callback could run, and the evidence for those surfaces simply never
|
|
12
|
+
* existed. A customer would have seen a grid with most of their application
|
|
13
|
+
* missing and no indication why.
|
|
14
|
+
*
|
|
15
|
+
* So teardown sweeps: for every page still open, if its current URL has not been
|
|
16
|
+
* scanned during this test, scan it now. The page has by definition finished
|
|
17
|
+
* settling — the test is over — which makes this the most reliable moment
|
|
18
|
+
* available, not a fallback.
|
|
19
|
+
*
|
|
20
|
+
* The settle heuristic still earns its place: it is what catches the surfaces a
|
|
21
|
+
* test passes THROUGH on its way somewhere else, which teardown can never see
|
|
22
|
+
* because they are no longer on screen.
|
|
23
|
+
*/
|
|
24
|
+
export declare function flushAutoScan(context: BrowserContext, options: ResolvedSetsquareOptions, testInfo: TestInfo, warn: (message: string) => void): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=autoscan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoscan.d.ts","sourceRoot":"","sources":["../src/autoscan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAavE,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AA+DhF,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,wBAAwB,EACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,IAAI,CAAC,CAqEf;AA6LD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,wBAAwB,EACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,IAAI,CAAC,CA0Df"}
|
package/dist/autoscan.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { engineVersion, rulesetDigest, runAxe } from './axe.js';
|
|
2
|
+
import { SCAN_RECORD_ATTACHMENT, SCAN_RECORD_VERSION } from './contract.js';
|
|
3
|
+
import { toFindings, toInapplicable, toPasses } from './findings.js';
|
|
4
|
+
import { runStatusMessages, runTextSpacing } from './checks/dynamic.js';
|
|
5
|
+
import { setsquareCheckVersions } from './checks/versions.js';
|
|
6
|
+
import { runReadingLevel } from './reading-level.js';
|
|
7
|
+
import { readingLevelEnabled } from './runtime.js';
|
|
8
|
+
import { capturePerf } from './perf.js';
|
|
9
|
+
import { readDocumentFacts } from './document.js';
|
|
10
|
+
import { pageRuntime } from './observe.js';
|
|
11
|
+
import { recordUnscannable } from './unscannable.js';
|
|
12
|
+
import { changedOnlyRoutes } from './options.js';
|
|
13
|
+
import { tagsFor } from './rules.js';
|
|
14
|
+
import { toUrlPattern } from './sanitise.js';
|
|
15
|
+
import { MUTATION_TOTAL, SETTLE_BINDING, settleScript } from './settle.js';
|
|
16
|
+
const state = new WeakMap();
|
|
17
|
+
let visitCounter = 0;
|
|
18
|
+
function stateFor(page, url) {
|
|
19
|
+
const existing = state.get(page);
|
|
20
|
+
if (existing && existing.visitUrl === url)
|
|
21
|
+
return existing;
|
|
22
|
+
const next = {
|
|
23
|
+
queue: existing?.queue ?? Promise.resolve(),
|
|
24
|
+
visitRef: `v${(visitCounter += 1)}`,
|
|
25
|
+
visitUrl: url,
|
|
26
|
+
perfCaptured: false,
|
|
27
|
+
scans: existing?.scans ?? 0,
|
|
28
|
+
scanned: existing?.scanned ?? new Map(),
|
|
29
|
+
};
|
|
30
|
+
state.set(page, next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A per-page ceiling on automatic scans.
|
|
35
|
+
*
|
|
36
|
+
* Without one, a page that mutates in bursts — a poller, a chat widget, an
|
|
37
|
+
* animation that pauses — produces a settle signal per burst and we scan it
|
|
38
|
+
* repeatedly for the whole test. The dedupe in the reporter would collapse the
|
|
39
|
+
* results, but the RUN TIME would already have been spent, and run time is the
|
|
40
|
+
* canary this milestone exists to listen to.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately generous: a genuinely multi-route test should not hit it. When it
|
|
43
|
+
* does, the run says so rather than silently capping.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_AUTO_SCANS_PER_PAGE = 25;
|
|
46
|
+
export function installAutoScan(context, options, testInfo, warn) {
|
|
47
|
+
const settle = options.settle;
|
|
48
|
+
const onSettled = async (page, signal) => {
|
|
49
|
+
// Nothing to record: a closed page cannot hold a ledger, and the page's own
|
|
50
|
+
// observation record has already been written or never will be.
|
|
51
|
+
if (page.isClosed())
|
|
52
|
+
return;
|
|
53
|
+
const pageState = stateFor(page, signal.url);
|
|
54
|
+
if (pageState.scans >= MAX_AUTO_SCANS_PER_PAGE) {
|
|
55
|
+
recordUnscannable(page, toUrlPattern(signal.url).pattern, 'scan-cap-reached');
|
|
56
|
+
if (pageState.scans === MAX_AUTO_SCANS_PER_PAGE) {
|
|
57
|
+
pageState.scans += 1;
|
|
58
|
+
warn(`A page settled more than ${MAX_AUTO_SCANS_PER_PAGE} times in one test ` +
|
|
59
|
+
`(${signal.url}); further automatic scans were skipped. This usually means ` +
|
|
60
|
+
`something on the page never stops changing.`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
pageState.scans += 1;
|
|
65
|
+
/*
|
|
66
|
+
* Queued, never concurrent.
|
|
67
|
+
*
|
|
68
|
+
* Playwright serialises operations on a page anyway, so a scan running while
|
|
69
|
+
* the customer's test is mid-click does not corrupt anything — but it does
|
|
70
|
+
* make their action WAIT for our axe run. Chaining scans keeps that cost
|
|
71
|
+
* bounded and predictable instead of multiplying it.
|
|
72
|
+
*/
|
|
73
|
+
pageState.queue = pageState.queue.then(async () => {
|
|
74
|
+
if (page.isClosed())
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
await scanSettledPage(page, signal, options, testInfo, warn);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Never the customer's problem. A failed automatic scan is a warning in
|
|
81
|
+
// our own output and nothing else — plus a counted ledger row, so the
|
|
82
|
+
// coverage figures cannot silently absorb it.
|
|
83
|
+
recordUnscannable(page, toUrlPattern(signal.url).pattern, 'scan-failed');
|
|
84
|
+
warn(`Automatic scan of ${signal.url} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
await pageState.queue;
|
|
88
|
+
};
|
|
89
|
+
return Promise.all([
|
|
90
|
+
/*
|
|
91
|
+
* A binding rather than polling.
|
|
92
|
+
*
|
|
93
|
+
* The alternative — asking the page every N milliseconds whether it has gone
|
|
94
|
+
* quiet — costs a round-trip per poll per page for the whole run, and still
|
|
95
|
+
* learns about the settle late. The binding costs one function on `window`,
|
|
96
|
+
* which is real pollution of a customer's page and is why it is namespaced
|
|
97
|
+
* and non-enumerable.
|
|
98
|
+
*/
|
|
99
|
+
context.exposeBinding(SETTLE_BINDING, ({ page }, signal) => onSettled(page, signal)),
|
|
100
|
+
context.addInitScript(settleScript(settle)),
|
|
101
|
+
]).then(() => undefined);
|
|
102
|
+
}
|
|
103
|
+
async function scanSettledPage(page, signal, options, testInfo, warn) {
|
|
104
|
+
const url = page.url();
|
|
105
|
+
/*
|
|
106
|
+
* The page moved on between settling and being scanned.
|
|
107
|
+
*
|
|
108
|
+
* Ordinary in a fast suite: a test clicks through two routes quicker than a
|
|
109
|
+
* scan takes. Attributing findings to the URL that settled would file them
|
|
110
|
+
* against a surface they did not come from, so the scan is abandoned instead.
|
|
111
|
+
* The new URL will settle and be scanned on its own.
|
|
112
|
+
*/
|
|
113
|
+
if (url !== signal.url) {
|
|
114
|
+
// Counted, not merely skipped. A fast suite that outruns its own scans looks
|
|
115
|
+
// identical to a smaller application unless the misses are on the record.
|
|
116
|
+
recordUnscannable(page, toUrlPattern(signal.url).pattern, 'nav-aborted');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const pageState = stateFor(page, url);
|
|
120
|
+
/*
|
|
121
|
+
* Changed-only mode: skip a surface the change cannot reach.
|
|
122
|
+
*
|
|
123
|
+
* The SDK does no route reasoning — it compares a pattern it already computed
|
|
124
|
+
* against a list the CLI wrote. A miss is RECORDED in the ledger rather than
|
|
125
|
+
* silently dropped, because a run that scanned a third of the app must not read
|
|
126
|
+
* like a run of a smaller app.
|
|
127
|
+
*
|
|
128
|
+
* Only a real array licenses skipping: `changedOnlyRoutes` returns undefined for
|
|
129
|
+
* a missing, null or malformed set, and undefined means scan everything.
|
|
130
|
+
*/
|
|
131
|
+
const changedOnly = changedOnlyRoutes();
|
|
132
|
+
if (changedOnly !== undefined) {
|
|
133
|
+
const pattern = toUrlPattern(url).pattern;
|
|
134
|
+
if (!changedOnly.has(pattern)) {
|
|
135
|
+
recordUnscannable(page, pattern, 'not-in-changed-set');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const scanStartedAt = Date.now();
|
|
140
|
+
const results = await runAxe(page, options.rules, options.scanTimeoutMs);
|
|
141
|
+
const axeMs = Date.now() - scanStartedAt;
|
|
142
|
+
/*
|
|
143
|
+
* The scan ran out of budget, so there is no scan.
|
|
144
|
+
*
|
|
145
|
+
* Recorded as a warning and NOT as an empty result: "we looked and found
|
|
146
|
+
* nothing" and "we ran out of time" are the two facts this product exists to
|
|
147
|
+
* keep apart, and conflating them here would be the confident false pass in its
|
|
148
|
+
* purest form.
|
|
149
|
+
*/
|
|
150
|
+
if (!results) {
|
|
151
|
+
recordUnscannable(page, toUrlPattern(url).pattern, 'scan-timed-out');
|
|
152
|
+
warn(`A scan of ${url} was abandoned after ${options.scanTimeoutMs}ms so it could not ` +
|
|
153
|
+
'hold up the test. That surface has no evidence in this run rather than a clean result.');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const axeVersion = engineVersion(results) ?? 'unknown';
|
|
157
|
+
const findings = toFindings(results);
|
|
158
|
+
// Setsquare's own checks, same channel and same never-fail discipline as
|
|
159
|
+
// the explicit-scan path. The digest names the run's configured check set,
|
|
160
|
+
// never which scans happened to execute one — see setsquareCheckVersions for why.
|
|
161
|
+
const setsquareChecks = setsquareCheckVersions(options.rules);
|
|
162
|
+
if (readingLevelEnabled()) {
|
|
163
|
+
const readingLevel = await runReadingLevel(page);
|
|
164
|
+
if (readingLevel)
|
|
165
|
+
findings.push(readingLevel);
|
|
166
|
+
}
|
|
167
|
+
const passes = toPasses(results);
|
|
168
|
+
const inapplicable = toInapplicable(results);
|
|
169
|
+
/*
|
|
170
|
+
* Dynamic checks only at TEARDOWN, never mid-test: the text-spacing probe
|
|
171
|
+
* mutates the live page, and an auto-scan firing between a customer's
|
|
172
|
+
* actions would make us the flake in their screenshot assertion. At
|
|
173
|
+
* teardown the test is over and the page is about to be discarded.
|
|
174
|
+
*/
|
|
175
|
+
if (signal.reason === 'teardown') {
|
|
176
|
+
for (const check of [await runTextSpacing(page), await runStatusMessages(page)]) {
|
|
177
|
+
if (check.finding)
|
|
178
|
+
findings.push(check.finding);
|
|
179
|
+
if (check.pass)
|
|
180
|
+
passes.push(check.pass);
|
|
181
|
+
if (check.inapplicable)
|
|
182
|
+
inapplicable.push(check.inapplicable);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const record = {
|
|
186
|
+
recordVersion: SCAN_RECORD_VERSION,
|
|
187
|
+
kind: 'scan',
|
|
188
|
+
page: { project: testInfo.project.name, engine: engineOf(page), url },
|
|
189
|
+
// Auto-scan states nothing. The surface is inferred by the reporter from the
|
|
190
|
+
// route, and any persona or theme comes from configuration or the live DOM —
|
|
191
|
+
// never from this file guessing.
|
|
192
|
+
declared: { surface: '' },
|
|
193
|
+
visitRef: pageState.visitRef,
|
|
194
|
+
axeVersion,
|
|
195
|
+
rulesetDigest: rulesetDigest(axeVersion, results, tagsFor(options.rules), setsquareChecks),
|
|
196
|
+
wcagTarget: options.rules.target,
|
|
197
|
+
findings,
|
|
198
|
+
passes,
|
|
199
|
+
inapplicable,
|
|
200
|
+
settle: {
|
|
201
|
+
reason: signal.reason,
|
|
202
|
+
...(signal.via ? { via: signal.via } : {}),
|
|
203
|
+
sinceNavMs: Math.round(signal.sinceNavMs),
|
|
204
|
+
mutations: signal.mutations,
|
|
205
|
+
settleTimedOut: signal.settleTimedOut,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
const viewport = page.viewportSize();
|
|
209
|
+
if (viewport)
|
|
210
|
+
record.page.viewport = viewport;
|
|
211
|
+
const probesStartedAt = Date.now();
|
|
212
|
+
// Theme and document facts in one round-trip. Two `page.evaluate` calls on a
|
|
213
|
+
// path that runs for every scan in the suite is a cost with nothing to show for it.
|
|
214
|
+
const facts = await readDocumentFacts(page);
|
|
215
|
+
if (facts.theme)
|
|
216
|
+
record.page.observedTheme = facts.theme;
|
|
217
|
+
if (facts.document)
|
|
218
|
+
record.document = facts.document;
|
|
219
|
+
/*
|
|
220
|
+
* Perf and page-runtime are both properties of the PAGE LOAD, not of the scan,
|
|
221
|
+
* so both are emitted once per visit and gated together.
|
|
222
|
+
*
|
|
223
|
+
* Scanning one page as its state changes — modal closed, then modal open —
|
|
224
|
+
* produces several scans from a single navigation. Repeating either block would
|
|
225
|
+
* weight pillar 3's averages by how many states a surface happens to have and
|
|
226
|
+
* would report the same 500 four times.
|
|
227
|
+
*/
|
|
228
|
+
if (!pageState.perfCaptured) {
|
|
229
|
+
const perf = await capturePerf(page);
|
|
230
|
+
if (perf)
|
|
231
|
+
record.perf = perf;
|
|
232
|
+
const runtime = pageRuntime(page);
|
|
233
|
+
if (runtime)
|
|
234
|
+
record.runtime = runtime;
|
|
235
|
+
// Set regardless of whether either block materialised: the gate means "this
|
|
236
|
+
// visit has had its page-load facts taken", and an engine that can measure
|
|
237
|
+
// neither must not be asked again on every subsequent state.
|
|
238
|
+
pageState.perfCaptured = true;
|
|
239
|
+
}
|
|
240
|
+
const probesMs = Date.now() - probesStartedAt;
|
|
241
|
+
record.cost = {
|
|
242
|
+
totalMs: Date.now() - scanStartedAt,
|
|
243
|
+
axeMs,
|
|
244
|
+
...(probesMs > 0 ? { probesMs } : {}),
|
|
245
|
+
};
|
|
246
|
+
pageState.scanned.set(url, await mutationTotal(page));
|
|
247
|
+
await testInfo.attach(SCAN_RECORD_ATTACHMENT, {
|
|
248
|
+
body: Buffer.from(JSON.stringify(record), 'utf8'),
|
|
249
|
+
contentType: 'application/json',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/** How much the page has changed in total. Zero if the counter is unavailable. */
|
|
253
|
+
async function mutationTotal(page) {
|
|
254
|
+
try {
|
|
255
|
+
return await page.evaluate((key) => window[key] ?? 0, MUTATION_TOTAL);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function engineOf(page) {
|
|
262
|
+
const name = page.context().browser()?.browserType().name();
|
|
263
|
+
return name === 'firefox' || name === 'webkit' ? name : 'chromium';
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Scan whatever the test left on screen, at teardown.
|
|
267
|
+
*
|
|
268
|
+
* This exists because of a measurement, and it is the single most important
|
|
269
|
+
* thing Stage 5 learned. Auto-scan fires 500ms after a page goes quiet — but a
|
|
270
|
+
* great many real tests finish in less than that. On the fixture suite, only two
|
|
271
|
+
* of ten surfaces were ever scanned: every fast test tore its context down before
|
|
272
|
+
* the settle callback could run, and the evidence for those surfaces simply never
|
|
273
|
+
* existed. A customer would have seen a grid with most of their application
|
|
274
|
+
* missing and no indication why.
|
|
275
|
+
*
|
|
276
|
+
* So teardown sweeps: for every page still open, if its current URL has not been
|
|
277
|
+
* scanned during this test, scan it now. The page has by definition finished
|
|
278
|
+
* settling — the test is over — which makes this the most reliable moment
|
|
279
|
+
* available, not a fallback.
|
|
280
|
+
*
|
|
281
|
+
* The settle heuristic still earns its place: it is what catches the surfaces a
|
|
282
|
+
* test passes THROUGH on its way somewhere else, which teardown can never see
|
|
283
|
+
* because they are no longer on screen.
|
|
284
|
+
*/
|
|
285
|
+
export async function flushAutoScan(context, options, testInfo, warn) {
|
|
286
|
+
for (const page of context.pages()) {
|
|
287
|
+
if (page.isClosed())
|
|
288
|
+
continue;
|
|
289
|
+
const url = page.url();
|
|
290
|
+
// `about:blank` is Playwright opening a context, not the suite visiting
|
|
291
|
+
// anything. Scanning it would put a surface in the grid nobody asked for.
|
|
292
|
+
if (!url || url === 'about:blank')
|
|
293
|
+
continue;
|
|
294
|
+
const pageState = stateFor(page, url);
|
|
295
|
+
/*
|
|
296
|
+
* Re-scan a URL already scanned during this test IF the page has changed
|
|
297
|
+
* since — which is the whole reason this tracks mutation counts rather than
|
|
298
|
+
* just which URLs were visited.
|
|
299
|
+
*
|
|
300
|
+
* Measured on the fixture site: `/slow` renders a conformant shell, goes
|
|
301
|
+
* quiet, is scanned at 500ms with zero violations, and only THEN injects its
|
|
302
|
+
* two real violations at 1000ms. Skipping the second scan because the URL was
|
|
303
|
+
* "already done" enshrined the shell and reported a clean page — the exact
|
|
304
|
+
* confident false pass this whole heuristic exists to prevent. The mutation
|
|
305
|
+
* counter distinguishes "already scanned" from "already scanned, and then
|
|
306
|
+
* everything changed".
|
|
307
|
+
*/
|
|
308
|
+
const lastScannedAt = pageState.scanned.get(url);
|
|
309
|
+
if (lastScannedAt !== undefined && (await mutationTotal(page)) === lastScannedAt)
|
|
310
|
+
continue;
|
|
311
|
+
try {
|
|
312
|
+
// Chained onto the same queue as settle-triggered scans, so a scan that was
|
|
313
|
+
// already in flight when the test ended is not raced.
|
|
314
|
+
const mutations = await mutationTotal(page);
|
|
315
|
+
pageState.queue = pageState.queue.then(() => scanSettledPage(page, {
|
|
316
|
+
url,
|
|
317
|
+
// Honest about where this came from. A teardown scan is not a settle,
|
|
318
|
+
// and labelling it one would make the diagnostics — whose entire job
|
|
319
|
+
// is telling you how much to trust a result — lie.
|
|
320
|
+
reason: 'teardown',
|
|
321
|
+
sinceNavMs: 0,
|
|
322
|
+
mutations,
|
|
323
|
+
settleTimedOut: false,
|
|
324
|
+
}, options, testInfo, warn));
|
|
325
|
+
await pageState.queue;
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
warn(`Final scan of ${url} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=autoscan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoscan.js","sourceRoot":"","sources":["../src/autoscan.ts"],"names":[],"mappings":"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"}
|
package/dist/axe.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import { type RulePolicy } from './rules.js';
|
|
3
|
+
/** The subset of axe's output this SDK reads. */
|
|
4
|
+
export interface AxeCheckResult {
|
|
5
|
+
data?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface AxeNode {
|
|
8
|
+
target: unknown[];
|
|
9
|
+
failureSummary?: string;
|
|
10
|
+
any?: AxeCheckResult[];
|
|
11
|
+
all?: AxeCheckResult[];
|
|
12
|
+
none?: AxeCheckResult[];
|
|
13
|
+
}
|
|
14
|
+
export interface AxeRuleResult {
|
|
15
|
+
id: string;
|
|
16
|
+
impact?: string | null;
|
|
17
|
+
tags: string[];
|
|
18
|
+
helpUrl?: string;
|
|
19
|
+
nodes: AxeNode[];
|
|
20
|
+
}
|
|
21
|
+
export interface AxeRunResults {
|
|
22
|
+
violations: AxeRuleResult[];
|
|
23
|
+
incomplete: AxeRuleResult[];
|
|
24
|
+
passes: AxeRuleResult[];
|
|
25
|
+
inapplicable: AxeRuleResult[];
|
|
26
|
+
testEngine?: {
|
|
27
|
+
name?: string;
|
|
28
|
+
version?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Inject axe and run it against the page's current state.
|
|
33
|
+
*
|
|
34
|
+
* Injection happens per scan rather than per page, because a navigation wipes
|
|
35
|
+
* the document and any previously injected script with it. Re-injecting is
|
|
36
|
+
* cheap next to the run itself.
|
|
37
|
+
*
|
|
38
|
+
* Main frame only, and that is a stated limitation rather than an oversight:
|
|
39
|
+
* axe reports cross-origin iframes as `incomplete` unless it is injected into
|
|
40
|
+
* each frame, which needs same-origin access the SDK cannot assume. Reporting
|
|
41
|
+
* them as incomplete is the honest outcome — "we could not check this" — and it
|
|
42
|
+
* reaches the payload as such rather than as a pass.
|
|
43
|
+
*
|
|
44
|
+
* ## Why this does not use `window.axe`
|
|
45
|
+
*
|
|
46
|
+
* Because it did, and it broke a customer's suite.
|
|
47
|
+
*
|
|
48
|
+
* The reference implementation runs `@axe-core/playwright` in its own specs. Both
|
|
49
|
+
* it and the original version of this function loaded axe onto the shared
|
|
50
|
+
* `window.axe`, so an auto-scan firing while one of their scans was in flight hit
|
|
51
|
+
* axe-core's own guard — *"Axe is already running"* — and failed two of THEIR
|
|
52
|
+
* tests, which then passed on retry. The SDK had changed a verdict in the suite
|
|
53
|
+
* of the customer profile most likely to adopt it: the one already running axe.
|
|
54
|
+
*
|
|
55
|
+
* The fix is that our instance is never reachable as `window.axe`. Axe's UMD
|
|
56
|
+
* bundle assigns itself to the global, so the injection captures it, files it
|
|
57
|
+
* under our own key, and puts back whatever was there before — including
|
|
58
|
+
* `undefined`, so a page with no axe of its own is left with no axe of its own.
|
|
59
|
+
* A separate instance carries its own `_running` flag, so neither side can ever
|
|
60
|
+
* trip the other's guard.
|
|
61
|
+
*
|
|
62
|
+
* Politeness on top of correctness: if the customer's axe IS mid-run we wait for
|
|
63
|
+
* it, up to a short budget, rather than traversing the same DOM at the same time.
|
|
64
|
+
* Two concurrent runs are no longer an ERROR, but they are still two full tree
|
|
65
|
+
* walks competing for one main thread, and the customer's run is the one with a
|
|
66
|
+
* person waiting on it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runAxe(page: Page, policy: RulePolicy, timeoutMs?: number): Promise<AxeRunResults | undefined>;
|
|
69
|
+
/** The axe build that produced a result, read from the run rather than assumed. */
|
|
70
|
+
export declare function engineVersion(results: AxeRunResults): string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* A digest of the rules that actually ran.
|
|
73
|
+
*
|
|
74
|
+
* Evidence claims are relative to a ruleset, so "this finding did not appear last
|
|
75
|
+
* week" is only meaningful if the same rules were evaluated both times. The
|
|
76
|
+
* digest is what makes that checkable rather than assumed.
|
|
77
|
+
*
|
|
78
|
+
* Computed from the union of every outcome bucket — violations, incompletes,
|
|
79
|
+
* passes and inapplicable — because that union IS the resolved rule set. axe
|
|
80
|
+
* ships 105 rules and evaluates ~89 by default; the remainder are experimental or
|
|
81
|
+
* off by default and never appear in any bucket. Digesting `getRules()` instead
|
|
82
|
+
* would name rules that never ran, and would not change when the default set did.
|
|
83
|
+
*
|
|
84
|
+
* Sorted before hashing so two runs of the same configuration agree regardless of
|
|
85
|
+
* the order axe happened to report in.
|
|
86
|
+
*/
|
|
87
|
+
export declare function rulesetDigest(axeVersion: string, results: AxeRunResults, tags?: string[],
|
|
88
|
+
/**
|
|
89
|
+
* Setsquare's own checks, when any are enabled — versioned ids like
|
|
90
|
+
* `setsquare-reading-level@1`. Evidence claims are relative to everything that
|
|
91
|
+
* assessed the page, not only axe, so two runs that differ in enabled setsquare
|
|
92
|
+
* checks must not share a digest. Empty (the default) leaves the digest
|
|
93
|
+
* byte-identical to what pre-check SDKs produced.
|
|
94
|
+
*/
|
|
95
|
+
setsquareChecks?: string[]): string;
|
|
96
|
+
//# sourceMappingURL=axe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../src/axe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AA0BtD,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,cAAc,EAAE,CAAC;IACvB,GAAG,CAAC,EAAE,cAAc,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,UAAU,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CA6GpC;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,EACtB,IAAI,GAAE,MAAM,EAAO;AACnB;;;;;;GAMG;AACH,eAAe,GAAE,MAAM,EAAO,GAC7B,MAAM,CAuBR"}
|