testaro 78.0.8 → 78.6.0
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/.gitattributes +94 -0
- package/.github/workflows/ci.yml +58 -0
- package/.github/workflows/publish.yml +50 -0
- package/.github/workflows/typescript.yml +35 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +24 -18
- package/CONTAINERS.md +9 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +143 -19
- package/UPGRADES.md +4 -0
- package/actSpecs-doc.md +13 -1
- package/actSpecs.js +29 -5
- package/call.js +6 -5
- package/dist/nameComputation.js +1 -0
- package/docker-compose.yml +8 -2
- package/docs/checkpoint-scanning.md +199 -0
- package/docs/standard-result-outcome.md +165 -0
- package/env.example +49 -10
- package/eslint.config.mjs +101 -0
- package/netWatch.js +372 -222
- package/package.json +46 -31
- package/pour/README.md +47 -0
- package/pour/pour.min.js +8 -0
- package/procs/actDo.js +808 -0
- package/procs/catalog.d.ts +17 -0
- package/procs/catalog.js +312 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +41 -0
- package/procs/config.js +98 -0
- package/procs/dateTime.js +2 -1
- package/procs/doActs.js +442 -849
- package/procs/doTestAct.js +27 -121
- package/procs/flow.js +221 -0
- package/procs/generateRuleRegistry.js +94 -0
- package/procs/getSource.d.ts +26 -0
- package/procs/job.js +115 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +243 -108
- package/procs/nu.d.ts +48 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- package/procs/standard.d.ts +18 -0
- package/procs/standard.js +83 -0
- package/procs/standard.ts +126 -0
- package/procs/testAct.js +145 -0
- package/procs/testaro.d.ts +20 -0
- package/procs/testaro.js +236 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +81 -69
- package/procs/xPath.ts +106 -0
- package/procs/xPathScript.d.ts +6 -0
- package/procs/xPathScript.js +70 -0
- package/run.js +3 -2
- package/surea11y/README.md +44 -0
- package/surea11y/surea11y.browser.js +14 -0
- package/testaro/adbID.d.ts +3 -0
- package/testaro/adbID.js +38 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +31 -34
- package/testaro/allCapStyle.ts +55 -0
- package/testaro/allCaps.d.ts +16 -0
- package/testaro/allCaps.js +179 -151
- package/testaro/allCaps.ts +228 -0
- package/testaro/allHidden.d.ts +13 -0
- package/testaro/allHidden.js +29 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +30 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +27 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +20 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +59 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +32 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +317 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +17 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +33 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +57 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +25 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +113 -104
- package/testaro/dupAtt.ts +144 -0
- package/testaro/elements.d.ts +6 -0
- package/testaro/elements.js +153 -153
- package/testaro/elements.ts +215 -0
- package/testaro/embAc.d.ts +3 -0
- package/testaro/embAc.js +19 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +185 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +100 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +64 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +28 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +61 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +54 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +163 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +155 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +14 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +17 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +31 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +17 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +43 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +79 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +13 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +25 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +22 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +30 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +41 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +92 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +39 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +16 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +18 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +36 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +57 -59
- package/testaro/radioSet.ts +79 -0
- package/testaro/registry.d.ts +63 -0
- package/testaro/registry.js +67 -0
- package/testaro/registry.ts +141 -0
- package/testaro/role.d.ts +3 -0
- package/testaro/role.js +27 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +30 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +248 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +272 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +130 -132
- package/testaro/targetsNear.ts +160 -0
- package/testaro/textNodes.d.ts +6 -0
- package/testaro/textNodes.js +139 -135
- package/testaro/textNodes.ts +185 -0
- package/testaro/textSem.d.ts +3 -0
- package/testaro/textSem.js +25 -28
- package/testaro/textSem.ts +47 -0
- package/testaro/title.d.ts +9 -0
- package/testaro/title.js +16 -13
- package/testaro/title.ts +31 -0
- package/testaro/titledEl.d.ts +3 -0
- package/testaro/titledEl.js +15 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +19 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +138 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +272 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +199 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +141 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +174 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +57 -0
- package/tests/ibm.js +182 -175
- package/tests/ibm.ts +287 -0
- package/tests/nuVal.d.ts +13 -0
- package/tests/nuVal.js +107 -112
- package/tests/nuVal.ts +145 -0
- package/tests/nuVnu.d.ts +15 -0
- package/tests/nuVnu.js +141 -111
- package/tests/nuVnu.ts +144 -0
- package/tests/pour.d.ts +31 -0
- package/tests/pour.js +242 -0
- package/tests/pour.ts +273 -0
- package/tests/qualWeb.d.ts +39 -0
- package/tests/qualWeb.js +262 -273
- package/tests/qualWeb.ts +344 -0
- package/tests/surea11y.d.ts +33 -0
- package/tests/surea11y.js +288 -0
- package/tests/surea11y.ts +334 -0
- package/tests/testaro.d.ts +25 -0
- package/tests/testaro.js +745 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +166 -177
- package/tests/wave.ts +252 -0
- package/tsconfig.json +17 -0
- package/types.d.ts +243 -0
- package/types.js +10 -0
- package/types.ts +376 -0
- package/validation/act/README.md +46 -0
- package/validation/act/capture.js +877 -0
- package/validation/act/chromium-issue-draft.md +66 -0
- package/validation/act/fp-triage-2026-08-22.md +86 -0
- package/validation/act/isolation-notes.md +159 -0
- package/validation/act/playwright-issue-draft.md +73 -0
- package/validation/act/propose-mappings.js +0 -0
- package/validation/act/repro-cdp-raw.js +105 -0
- package/validation/act/repro-metarefresh.js +65 -0
- package/validation/act/score.js +184 -0
- package/validation/act/stage3a-stress-report.md +69 -0
- package/validation/act/stage3b-mapping-proposals.md +94 -0
- package/validation/act/stage3b-triage-draft.md +138 -0
- package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
- package/validation/executors/netWatch.js +180 -90
- package/validation/executors/test.js +17 -2
- package/validation/executors/tests.js +118 -10
- package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
- package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
- package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
- package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
- package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
- package/validation/knownFailures.json +1 -0
- package/validation/tests/jobProperties/adbID.json +27 -2
- package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
- package/validation/tests/jobProperties/allCaps.json +25 -0
- package/validation/tests/jobProperties/allHidden.json +133 -13
- package/validation/tests/jobProperties/allSlanted.json +3 -3
- package/validation/tests/jobProperties/altScheme.json +23 -8
- package/validation/tests/jobProperties/attVal.json +57 -57
- package/validation/tests/jobProperties/autocomplete.json +11 -1
- package/validation/tests/jobProperties/bulk.json +6 -1
- package/validation/tests/jobProperties/buttonMenu.json +63 -42
- package/validation/tests/jobProperties/captionLoc.json +1 -6
- package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
- package/validation/tests/jobProperties/checkpoint-page.json +397 -0
- package/validation/tests/jobProperties/checkpoint.json +395 -0
- package/validation/tests/jobProperties/datalistRef.json +20 -5
- package/validation/tests/jobProperties/distortion.json +28 -3
- package/validation/tests/jobProperties/docType.json +2 -2
- package/validation/tests/jobProperties/dupAtt.json +48 -33
- package/validation/tests/jobProperties/elements.json +28 -28
- package/validation/tests/jobProperties/embAc.json +36 -31
- package/validation/tests/jobProperties/focAndOp.json +284 -0
- package/validation/tests/jobProperties/focInd.json +34 -34
- package/validation/tests/jobProperties/focVis.json +1 -1
- package/validation/tests/jobProperties/hover.json +39 -37
- package/validation/tests/jobProperties/hr.json +13 -3
- package/validation/tests/jobProperties/imageLink.json +0 -5
- package/validation/tests/jobProperties/labClash.json +68 -33
- package/validation/tests/jobProperties/legendLoc.json +1 -6
- package/validation/tests/jobProperties/lineHeight.json +2 -2
- package/validation/tests/jobProperties/linkAmb.json +13 -18
- package/validation/tests/jobProperties/linkExt.json +1 -1
- package/validation/tests/jobProperties/linkOldAtt.json +12 -2
- package/validation/tests/jobProperties/linkTo.json +1 -1
- package/validation/tests/jobProperties/linkUl.json +65 -65
- package/validation/tests/jobProperties/miniText.json +7 -2
- package/validation/tests/jobProperties/motion.json +4 -50
- package/validation/tests/jobProperties/nonTable.json +48 -3
- package/validation/tests/jobProperties/optRoleSel.json +12 -2
- package/validation/tests/jobProperties/phOnly.json +6 -16
- package/validation/tests/jobProperties/pseudoP.json +17 -2
- package/validation/tests/jobProperties/radioSet.json +30 -30
- package/validation/tests/jobProperties/role.json +25 -5
- package/validation/tests/jobProperties/secHeading.json +26 -21
- package/validation/tests/jobProperties/styleDiff.json +35 -5
- package/validation/tests/jobProperties/tabNav.json +3 -1
- package/validation/tests/jobProperties/targetsNear.json +237 -0
- package/validation/tests/jobProperties/textNodes.json +29 -29
- package/validation/tests/jobProperties/textSem.json +26 -1
- package/validation/tests/jobProperties/title.json +12 -2
- package/validation/tests/jobProperties/titledEl.json +39 -9
- package/validation/tests/jobProperties/userPath.json +317 -0
- package/validation/tests/jobProperties/zIndex.json +40 -35
- package/validation/tests/targets/allCapStyle/index.html +26 -0
- package/validation/tests/targets/checkpoint/index.html +38 -0
- package/validation/tests/targets/datalistRef/index.html +1 -1
- package/validation/tests/targets/focAndOp/bad.html +29 -0
- package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
- package/validation/tests/targets/focInd/bad.html +2 -1
- package/validation/tests/targets/headEl/index.html +10 -1
- package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
- package/validation/tests/targets/userPath/index.html +42 -0
- package/validation/validateTest.js +61 -9
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- package/validation/tests/jobProperties/linkTitle.json +0 -127
- package/validation/tests/jobProperties/opFoc.json +0 -164
- package/validation/tests/jobProperties/targetSmall.json +0 -152
- package/validation/tests/jobProperties/targetTiny.json +0 -142
- package/validation/tests/targets/focOp/bad.html +0 -25
- package/validation/tests/targets/linkTitle/index.html +0 -24
- package/validation/tests/targets/opFoc/bad.html +0 -26
- package/validation/tests/targets/opFoc/good.html +0 -23
package/tests/qualWeb.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jeff Witt.
|
|
4
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
5
|
+
|
|
6
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
7
|
+
https://opensource.org/license/mit/ for details.
|
|
8
|
+
|
|
9
|
+
SPDX-License-Identifier: MIT
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// IMPORTS
|
|
13
|
+
|
|
14
|
+
import type {Page} from 'playwright';
|
|
15
|
+
import {getAttributeXPath, getXPathCatalogIndex} from '../procs/xPath';
|
|
16
|
+
import {getStandardResult, addInstance} from '../procs/standard';
|
|
17
|
+
import type {Act, Report, StandardInstance, StandardResult} from '../types';
|
|
18
|
+
import {readFileSync} from 'fs';
|
|
19
|
+
|
|
20
|
+
// TYPES
|
|
21
|
+
|
|
22
|
+
// The qualWeb-act properties this reporter consumes.
|
|
23
|
+
interface QualWebAct extends Act {
|
|
24
|
+
rules?: string[];
|
|
25
|
+
}
|
|
26
|
+
// The section names of a QualWeb report.
|
|
27
|
+
type QwSection = 'act-rules' | 'wcag-techniques' | 'best-practices';
|
|
28
|
+
// One element reported by a QualWeb assertion result.
|
|
29
|
+
interface QwElement {
|
|
30
|
+
htmlCode?: string;
|
|
31
|
+
}
|
|
32
|
+
// One result of a QualWeb rule assertion.
|
|
33
|
+
interface QwRaResult {
|
|
34
|
+
verdict: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
elements?: QwElement[];
|
|
37
|
+
}
|
|
38
|
+
// The assertions for one QualWeb rule.
|
|
39
|
+
interface QwRuleAssertions {
|
|
40
|
+
metadata?: {
|
|
41
|
+
warning?: number;
|
|
42
|
+
failed?: number;
|
|
43
|
+
};
|
|
44
|
+
results: QwRaResult[];
|
|
45
|
+
}
|
|
46
|
+
// One module section of a QualWeb report.
|
|
47
|
+
interface QwModuleReport {
|
|
48
|
+
assertions?: Record<string, QwRuleAssertions>;
|
|
49
|
+
}
|
|
50
|
+
// The native result: the module reports, keyed by section, as in QualWeb's own report.
|
|
51
|
+
interface QwNativeResult {
|
|
52
|
+
system?: {url?: string; evaluation?: string};
|
|
53
|
+
modules?: Record<string, QwModuleReport>;
|
|
54
|
+
}
|
|
55
|
+
// The options of one section's runner (QualWeb's EvaluationModule options).
|
|
56
|
+
interface QwSectionOptions {
|
|
57
|
+
include?: string[];
|
|
58
|
+
exclude?: string[];
|
|
59
|
+
levels?: string[];
|
|
60
|
+
principles?: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// CONSTANTS
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
QualWeb runs in the page under test, as @qualweb/core runs it in its own browser: the
|
|
67
|
+
page-side bundles first, then one bundle per section. It used to be given page.content() and
|
|
68
|
+
run by @qualweb/core, which loads that HTML with setContent() into a blank page — so relative
|
|
69
|
+
stylesheet links resolved nowhere, the page it tested was unstyled, and every CSS-dependent
|
|
70
|
+
rule (contrast, visibility, text spacing…) judged a page the user never sees. The bundles are
|
|
71
|
+
evaluated (not added as script tags), so the page's Content Security Policy does not apply.
|
|
72
|
+
*/
|
|
73
|
+
const readBundle = (name: string): string => readFileSync(require.resolve(name), 'utf8');
|
|
74
|
+
let pageBundles: string[] | null = null;
|
|
75
|
+
const sectionBundles: Partial<Record<QwSection, string>> = {};
|
|
76
|
+
const sectionPackages: Record<QwSection, string> = {
|
|
77
|
+
'act-rules': '@qualweb/act-rules',
|
|
78
|
+
'wcag-techniques': '@qualweb/wcag-techniques',
|
|
79
|
+
'best-practices': '@qualweb/best-practices'
|
|
80
|
+
};
|
|
81
|
+
// Rules a section may exclude after they crash before the section is given up on.
|
|
82
|
+
const MAX_EXCLUDED = 5;
|
|
83
|
+
// Mapping of QualWeb module violation types to ordinal severities.
|
|
84
|
+
const ordinalSeverities: Record<QwSection, Record<string, StandardInstance['ordinalSeverity']>> = {
|
|
85
|
+
'act-rules': {
|
|
86
|
+
'warning': 1,
|
|
87
|
+
'failed': 3
|
|
88
|
+
},
|
|
89
|
+
'wcag-techniques': {
|
|
90
|
+
'warning': 0,
|
|
91
|
+
'failed': 2
|
|
92
|
+
},
|
|
93
|
+
'best-practices': {
|
|
94
|
+
'warning': 0,
|
|
95
|
+
'failed': 1
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
qualWeb
|
|
101
|
+
Implements the QualWeb ruleset for accessibility.
|
|
102
|
+
Compiled to qualWeb.js by tsc (issue #73); edit this file, not the emitted one.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
// FUNCTIONS
|
|
106
|
+
|
|
107
|
+
// Conducts and reports the QualWeb tests.
|
|
108
|
+
export const reporter = async (page: Page, report: Report, actIndex: number, timeLimit: number) => {
|
|
109
|
+
const act = report.acts[actIndex] as QualWebAct;
|
|
110
|
+
const {rules} = act;
|
|
111
|
+
// Initialize the act report.
|
|
112
|
+
const data: {prevented?: boolean; error?: string; rulePreventions?: Record<string, string>} = {};
|
|
113
|
+
const result: {nativeResult: QwNativeResult; standardResult: StandardResult} = {
|
|
114
|
+
nativeResult: {},
|
|
115
|
+
standardResult: {}
|
|
116
|
+
};
|
|
117
|
+
const standard = report.standard !== 'no';
|
|
118
|
+
// If standard results are to be reported:
|
|
119
|
+
if (standard) {
|
|
120
|
+
// Initialize the standard result.
|
|
121
|
+
result.standardResult = getStandardResult();
|
|
122
|
+
}
|
|
123
|
+
/*
|
|
124
|
+
QualWeb tests HTML documents only: on anything else (e.g. an SVG image) its rules report
|
|
125
|
+
false results such as a missing lang on the html element.
|
|
126
|
+
*/
|
|
127
|
+
const rootNamespace = await page.evaluate(() => document.documentElement?.namespaceURI ?? null)
|
|
128
|
+
.catch(() => null);
|
|
129
|
+
// If the document is not HTML:
|
|
130
|
+
if (rootNamespace !== 'http://www.w3.org/1999/xhtml') {
|
|
131
|
+
// Report this.
|
|
132
|
+
return {
|
|
133
|
+
data: {
|
|
134
|
+
prevented: true,
|
|
135
|
+
error: `qualWeb tests HTML documents only (root namespace ${rootNamespace})`
|
|
136
|
+
},
|
|
137
|
+
result
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Specify, per section, whether and what to test (a rules spec of 'act:', 'wcag:' or 'best:'
|
|
141
|
+
// alone skips that section; with numbers, it tests only those rules).
|
|
142
|
+
const allAAA = {
|
|
143
|
+
levels: ['A', 'AA', 'AAA'],
|
|
144
|
+
principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
|
|
145
|
+
};
|
|
146
|
+
const sectionSpec = (prefix: string, codePrefix: string, all: QwSectionOptions): QwSectionOptions | null => {
|
|
147
|
+
const spec = rules?.find(typeRules => typeRules.startsWith(prefix));
|
|
148
|
+
if (spec === undefined) {
|
|
149
|
+
return all;
|
|
150
|
+
}
|
|
151
|
+
if (spec === prefix) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return {include: spec.slice(prefix.length).split(',').map(num => `${codePrefix}${num}`)};
|
|
155
|
+
};
|
|
156
|
+
const sections: Partial<Record<QwSection, QwSectionOptions>> = {};
|
|
157
|
+
for (const [section, options] of [
|
|
158
|
+
['act-rules', sectionSpec('act:', 'QW-ACT-R', allAAA)],
|
|
159
|
+
['wcag-techniques', sectionSpec('wcag:', 'QW-WCAG-T', allAAA)],
|
|
160
|
+
['best-practices', sectionSpec('best:', 'QW-BP', {})]
|
|
161
|
+
] as [QwSection, QwSectionOptions | null][]) {
|
|
162
|
+
if (options) {
|
|
163
|
+
sections[section] = options;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const deadline = Date.now() + timeLimit * 1000;
|
|
167
|
+
try {
|
|
168
|
+
// Load QualWeb into the page.
|
|
169
|
+
pageBundles ??= ['@qualweb/qw-page', '@qualweb/util', '@qualweb/locale'].map(readBundle);
|
|
170
|
+
for (const source of pageBundles) {
|
|
171
|
+
await page.evaluate(source);
|
|
172
|
+
}
|
|
173
|
+
// The page's own source, which QualWeb's meta-element rules read.
|
|
174
|
+
const sourceHtml = await page.content();
|
|
175
|
+
const modules: Record<string, QwModuleReport> = {};
|
|
176
|
+
const rulePreventions: Record<string, string> = {};
|
|
177
|
+
// For each section to be tested:
|
|
178
|
+
for (const [section, options] of Object.entries(sections) as [QwSection, QwSectionOptions][]) {
|
|
179
|
+
if (Date.now() > deadline) {
|
|
180
|
+
throw new Error(`time limit of ${timeLimit}s reached before ${section}`);
|
|
181
|
+
}
|
|
182
|
+
sectionBundles[section] ??= readBundle(sectionPackages[section]);
|
|
183
|
+
await page.evaluate(sectionBundles[section]!);
|
|
184
|
+
/*
|
|
185
|
+
One rule that throws takes its whole section down with it (e.g. QW-ACT-R76 in 0.8.5 on
|
|
186
|
+
lab(), oklab(), color() or color-mix() colours, qualweb/qualweb#352), so a rule named in
|
|
187
|
+
the stack trace of a crash is excluded and the section rerun; a crash that names no rule
|
|
188
|
+
prevents only its section.
|
|
189
|
+
*/
|
|
190
|
+
const outcome = await page.evaluate(({section, options, sourceHtml, maxExcluded}) => {
|
|
191
|
+
// Only this section's bundle has been loaded, so name only its runner.
|
|
192
|
+
const Runner = section === 'act-rules'
|
|
193
|
+
// @ts-expect-error: defined by the section bundle evaluated above.
|
|
194
|
+
? ACTRulesRunner
|
|
195
|
+
: section === 'wcag-techniques'
|
|
196
|
+
// @ts-expect-error: defined by the section bundle evaluated above.
|
|
197
|
+
? WCAGTechniquesRunner
|
|
198
|
+
// @ts-expect-error: defined by the section bundle evaluated above.
|
|
199
|
+
: BestPracticesRunner;
|
|
200
|
+
const preventions: Record<string, string> = {};
|
|
201
|
+
const exclude: string[] = [];
|
|
202
|
+
for (;;) {
|
|
203
|
+
try {
|
|
204
|
+
const opts = exclude.length ? {...options, exclude: [...(options.exclude ?? []), ...exclude]} : options;
|
|
205
|
+
const runner = new Runner(opts, 'en').configure(opts).test({sourceHtml, newTabWasOpen: false});
|
|
206
|
+
(window as any).__testaroQwRunners = {...(window as any).__testaroQwRunners, [section]: runner};
|
|
207
|
+
// QualWeb core keeps the act-rules runner here for its special-case pass.
|
|
208
|
+
if (section === 'act-rules') {
|
|
209
|
+
(window as any).act = runner;
|
|
210
|
+
}
|
|
211
|
+
return {ok: true, preventions};
|
|
212
|
+
}
|
|
213
|
+
catch(error) {
|
|
214
|
+
const message = String((error as Error)?.message ?? error).slice(0, 200);
|
|
215
|
+
// Rule classes are named QW_ACT_R76, QW_WCAG_T23, QW_BP1.
|
|
216
|
+
const rule = String((error as Error)?.stack ?? '').match(/QW_(?:ACT_R|WCAG_T|BP)\d+/)?.[0].replace(/_/g, '-');
|
|
217
|
+
if (! rule || exclude.includes(rule) || exclude.length >= maxExcluded) {
|
|
218
|
+
preventions[section] = `section failed: ${message}`;
|
|
219
|
+
return {ok: false, preventions};
|
|
220
|
+
}
|
|
221
|
+
preventions[rule] = message;
|
|
222
|
+
exclude.push(rule);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}, {section, options, sourceHtml, maxExcluded: MAX_EXCLUDED});
|
|
226
|
+
Object.assign(rulePreventions, outcome.preventions);
|
|
227
|
+
if (! outcome.ok) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
/*
|
|
231
|
+
QualWeb core's special case for act-rules: QW-ACT-R40 (zoomed text not clipped) is
|
|
232
|
+
re-tested at a 640 × 512 viewport, then the viewport is restored.
|
|
233
|
+
*/
|
|
234
|
+
const include = options.include;
|
|
235
|
+
if (section === 'act-rules' && (! include || include.includes('QW-ACT-R40'))) {
|
|
236
|
+
const viewport = page.viewportSize();
|
|
237
|
+
if (viewport) {
|
|
238
|
+
try {
|
|
239
|
+
await page.setViewportSize({width: 640, height: 512});
|
|
240
|
+
await page.evaluate(() => (window as any).act?.testSpecial?.());
|
|
241
|
+
}
|
|
242
|
+
catch(error) {
|
|
243
|
+
rulePreventions['QW-ACT-R40'] = `special-case pass failed: ${String((error as Error).message).slice(0, 200)}`;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
await page.setViewportSize(viewport);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
modules[section] = await page.evaluate(
|
|
251
|
+
section => JSON.parse(JSON.stringify((window as any).__testaroQwRunners[section].getReport())),
|
|
252
|
+
section
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (Object.keys(rulePreventions).length) {
|
|
256
|
+
data.rulePreventions = rulePreventions;
|
|
257
|
+
}
|
|
258
|
+
// Add the module reports to the result.
|
|
259
|
+
result.nativeResult = {system: {url: page.url(), evaluation: 'in-page'}, modules};
|
|
260
|
+
const {standardResult} = result;
|
|
261
|
+
// For each section that was to be tested:
|
|
262
|
+
for (const section of Object.keys(sections) as QwSection[]) {
|
|
263
|
+
const moduleReport = modules[section];
|
|
264
|
+
/*
|
|
265
|
+
A section that failed as a whole is recorded in rulePreventions; the act is prevented
|
|
266
|
+
only if no section ran.
|
|
267
|
+
*/
|
|
268
|
+
if (! moduleReport) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const {assertions} = moduleReport;
|
|
272
|
+
// If it contains assertions (test results):
|
|
273
|
+
if (assertions) {
|
|
274
|
+
const ruleIDs = Object.keys(assertions);
|
|
275
|
+
// For each rule:
|
|
276
|
+
for (const ruleID of ruleIDs) {
|
|
277
|
+
const ruleAssertions = assertions[ruleID];
|
|
278
|
+
const {metadata} = ruleAssertions;
|
|
279
|
+
// If there were any warnings or failures:
|
|
280
|
+
if (metadata?.warning || metadata?.failed) {
|
|
281
|
+
// Delete nonviolations from the results.
|
|
282
|
+
ruleAssertions.results = ruleAssertions.results.filter(
|
|
283
|
+
raResult => raResult.verdict !== 'passed'
|
|
284
|
+
);
|
|
285
|
+
// For each test result:
|
|
286
|
+
for (const raResult of ruleAssertions.results) {
|
|
287
|
+
const {elements, verdict} = raResult;
|
|
288
|
+
// If any violations are reported:
|
|
289
|
+
if (elements?.length) {
|
|
290
|
+
// For each violating element:
|
|
291
|
+
for (const element of elements) {
|
|
292
|
+
// Limit the size of its reported excerpt.
|
|
293
|
+
if ((element.htmlCode?.length as number) > 2000) {
|
|
294
|
+
element.htmlCode = `${element.htmlCode!.slice(0, 2000)} …`;
|
|
295
|
+
}
|
|
296
|
+
// If standard results are to be reported:
|
|
297
|
+
if (standard) {
|
|
298
|
+
const what = `[${verdict}] ${raResult.description}`;
|
|
299
|
+
const xPath = getAttributeXPath(element.htmlCode);
|
|
300
|
+
// Add an instance to the standard result.
|
|
301
|
+
addInstance(standardResult, {
|
|
302
|
+
ruleID,
|
|
303
|
+
what,
|
|
304
|
+
ordinalSeverity: ordinalSeverities[section][verdict],
|
|
305
|
+
outcome: verdict === 'warning' ? 'cantTell' : 'failed',
|
|
306
|
+
catalogIndex: getXPathCatalogIndex(report, xPath)
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Otherwise, i.e. if there were no warnings or failures:
|
|
314
|
+
else {
|
|
315
|
+
// Delete the rule.
|
|
316
|
+
delete assertions[ruleID];
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If no section ran:
|
|
322
|
+
if (! Object.keys(modules).length) {
|
|
323
|
+
// Report this.
|
|
324
|
+
data.prevented = true;
|
|
325
|
+
data.error = `No qualWeb section ran (${JSON.stringify(rulePreventions).slice(0, 300)})`;
|
|
326
|
+
}
|
|
327
|
+
// Test whether the result is an object.
|
|
328
|
+
try {
|
|
329
|
+
JSON.stringify(result);
|
|
330
|
+
}
|
|
331
|
+
catch(error) {
|
|
332
|
+
data.prevented = true;
|
|
333
|
+
data.error = `QualWeb result cannot be made JSON (${(error as Error).message})`;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch(error) {
|
|
337
|
+
data.prevented = true;
|
|
338
|
+
data.error = `QualWeb failed (${(error as Error).message})`;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
data,
|
|
342
|
+
result
|
|
343
|
+
};
|
|
344
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { Report, StandardResult } from '../types';
|
|
3
|
+
type SureSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
|
|
4
|
+
interface SureFinding {
|
|
5
|
+
ruleID: string;
|
|
6
|
+
what: string;
|
|
7
|
+
wcag: string;
|
|
8
|
+
severity: SureSeverity;
|
|
9
|
+
uncertainty: string;
|
|
10
|
+
needed: string;
|
|
11
|
+
html: string;
|
|
12
|
+
cssPath: string;
|
|
13
|
+
xPath: string;
|
|
14
|
+
}
|
|
15
|
+
interface SureNativeResult {
|
|
16
|
+
engine?: Record<string, unknown>;
|
|
17
|
+
violations?: SureFinding[];
|
|
18
|
+
incomplete?: SureFinding[];
|
|
19
|
+
passRuleCount?: number;
|
|
20
|
+
inapplicableRuleCount?: number;
|
|
21
|
+
manualRuleCount?: number;
|
|
22
|
+
manualOccurrenceCount?: number;
|
|
23
|
+
prevented?: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const reporter: (page: Page, report: Report, actIndex: number) => Promise<{
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
result: {
|
|
29
|
+
nativeResult: SureNativeResult;
|
|
30
|
+
standardResult: StandardResult;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
© 2026 Jeff Witt.
|
|
4
|
+
|
|
5
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
|
+
https://opensource.org/license/mit/ for details.
|
|
7
|
+
|
|
8
|
+
SPDX-License-Identifier: MIT
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.reporter = void 0;
|
|
45
|
+
// IMPORTS
|
|
46
|
+
const fs = __importStar(require("fs/promises"));
|
|
47
|
+
const xPath_1 = require("../procs/xPath");
|
|
48
|
+
const standard_1 = require("../procs/standard");
|
|
49
|
+
/*
|
|
50
|
+
surea11y
|
|
51
|
+
Implements the SureA11y core engine (https://github.com/SureA11y/core) WCAG ruleset.
|
|
52
|
+
Compiled to surea11y.js by tsc (issue #73); edit this file, not the emitted one.
|
|
53
|
+
|
|
54
|
+
Upstream publishes a self-contained standalone browser bundle, vendored verbatim at
|
|
55
|
+
../surea11y/surea11y.browser.js (MPL-2.0), exposing an `a11ycore` global with
|
|
56
|
+
`runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly)`. See
|
|
57
|
+
../surea11y/README.md for the pinned version and verified result shape.
|
|
58
|
+
|
|
59
|
+
Instance policy (engine-candidacy-pipeline.md, Stage 1): `fail`-tier occurrences become
|
|
60
|
+
instances with outcome `failed` at ordinalSeverity 2-3; automatic-rule `cantTell`-tier
|
|
61
|
+
occurrences become instances with outcome `cantTell` at 0-1 (the engine grades per occurrence
|
|
62
|
+
via `occurrenceOutcome`, which takes precedence over the rule-level outcome), carrying the
|
|
63
|
+
engine's `uncertainty` code and `needed` guidance when it supplies them; `pass` and
|
|
64
|
+
`notApplicable` results and all `type: 'manual'` rules are tallied in `data` only and never
|
|
65
|
+
become instances.
|
|
66
|
+
*/
|
|
67
|
+
// CONSTANTS
|
|
68
|
+
// Severity weights within a certainty band, mirroring tests/axe.ts.
|
|
69
|
+
const severityWeights = {
|
|
70
|
+
minor: 0,
|
|
71
|
+
moderate: 0,
|
|
72
|
+
serious: 1,
|
|
73
|
+
critical: 1
|
|
74
|
+
};
|
|
75
|
+
// FUNCTIONS
|
|
76
|
+
// Performs and reports the SureA11y tests.
|
|
77
|
+
const reporter = async (page, report, actIndex) => {
|
|
78
|
+
// Get the nonce, if any.
|
|
79
|
+
const act = report.acts[actIndex];
|
|
80
|
+
const { jobData } = report;
|
|
81
|
+
const scriptNonce = (jobData && jobData.lastScriptNonce);
|
|
82
|
+
// Initialize the act report.
|
|
83
|
+
let data = {};
|
|
84
|
+
const result = {
|
|
85
|
+
nativeResult: {},
|
|
86
|
+
standardResult: {}
|
|
87
|
+
};
|
|
88
|
+
const standard = report.standard !== 'no';
|
|
89
|
+
// If standard results are to be reported:
|
|
90
|
+
if (standard) {
|
|
91
|
+
// Initialize the standard result.
|
|
92
|
+
result.standardResult = (0, standard_1.getStandardResult)();
|
|
93
|
+
}
|
|
94
|
+
// Get the vendored tool bundle.
|
|
95
|
+
let script;
|
|
96
|
+
try {
|
|
97
|
+
script = await fs.readFile(`${__dirname}/../surea11y/surea11y.browser.js`, 'utf8');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
data.prevented = true;
|
|
101
|
+
data.error = 'ERROR: surea11y bundle missing (see surea11y/README.md for vendoring instructions)';
|
|
102
|
+
return { data, result };
|
|
103
|
+
}
|
|
104
|
+
// If the act is scoped to changed subtrees, the engine tests their nearest common ancestor
|
|
105
|
+
// (its context is one selector, resolved with querySelector).
|
|
106
|
+
const contextSelector = report.scope?.commonRoot || null;
|
|
107
|
+
// Perform the tests and populate the native result.
|
|
108
|
+
result.nativeResult = await page.evaluate(args => new Promise(async (resolve) => {
|
|
109
|
+
const { scriptNonce, script, contextSelector } = args;
|
|
110
|
+
// Detect page-CSP blocking of the injection (the violation event is
|
|
111
|
+
// dispatched as a task, so the check below waits a tick).
|
|
112
|
+
let cspBlocked = false;
|
|
113
|
+
document.addEventListener('securitypolicyviolation', event => {
|
|
114
|
+
if ((event.violatedDirective || '').startsWith('script-src')) {
|
|
115
|
+
cspBlocked = true;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Add the tool script to the page.
|
|
119
|
+
const toolScript = document.createElement('script');
|
|
120
|
+
if (scriptNonce) {
|
|
121
|
+
toolScript.nonce = scriptNonce;
|
|
122
|
+
console.log(`Added nonce ${scriptNonce} to tool script`);
|
|
123
|
+
}
|
|
124
|
+
toolScript.textContent = script;
|
|
125
|
+
document.body.insertAdjacentElement('beforeend', toolScript);
|
|
126
|
+
// If the bundle failed to define its global:
|
|
127
|
+
if (typeof a11ycore === 'undefined' || typeof a11ycore.runa11yCoreInPage !== 'function') {
|
|
128
|
+
// Let a pending securitypolicyviolation event fire before diagnosing.
|
|
129
|
+
await new Promise(tick => setTimeout(tick, 0));
|
|
130
|
+
// A CSP block is a per-page condition, not a browser fault — report it
|
|
131
|
+
// distinctly (wedge canaries must not match this message).
|
|
132
|
+
resolve({
|
|
133
|
+
prevented: true,
|
|
134
|
+
error: cspBlocked
|
|
135
|
+
? 'surea11y injection blocked by page CSP (script-src)'
|
|
136
|
+
: `a11ycore global not defined (contentType ${document.contentType}, `
|
|
137
|
+
+ `scriptChars ${script.length}, readyState ${document.readyState})`
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
// Run the engine on the whole page with default options — every loaded
|
|
143
|
+
// rule at the default WCAG 2.2 target, matching the axe and pour
|
|
144
|
+
// adapters' kitchen-sink default; testilo's taxonomy decides per rule
|
|
145
|
+
// what counts downstream. runa11yCoreInPage is synchronous; await
|
|
146
|
+
// tolerates a future promise-returning version.
|
|
147
|
+
const sureReport = await a11ycore.runa11yCoreInPage(location.href, contextSelector, {}, null);
|
|
148
|
+
// Dotted WCAG criterion (e.g. 1.4.3) from a rule's normative mappings.
|
|
149
|
+
const criterionOf = (meta) => {
|
|
150
|
+
const mappings = meta && meta.normativeMappings || [];
|
|
151
|
+
for (const mapping of mappings) {
|
|
152
|
+
const requirement = String(mapping.requirement || '').replace(/^SC\s*/i, '');
|
|
153
|
+
if (/^\d+\.\d+\.\d+$/.test(requirement)) {
|
|
154
|
+
return requirement;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return '';
|
|
158
|
+
};
|
|
159
|
+
const violations = [];
|
|
160
|
+
const incomplete = [];
|
|
161
|
+
// Tallied-only buckets.
|
|
162
|
+
let passRuleCount = 0;
|
|
163
|
+
let inapplicableRuleCount = 0;
|
|
164
|
+
let manualRuleCount = 0;
|
|
165
|
+
let manualOccurrenceCount = 0;
|
|
166
|
+
// For each atomic rule that ran:
|
|
167
|
+
(sureReport.checksResults || []).forEach(check => {
|
|
168
|
+
const { ruleId, outcome, type, occurrences } = check;
|
|
169
|
+
// Manual rules are advisory by definition: tally only.
|
|
170
|
+
if (type === 'manual') {
|
|
171
|
+
manualRuleCount++;
|
|
172
|
+
manualOccurrenceCount += (occurrences || []).length;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (outcome === 'pass') {
|
|
176
|
+
passRuleCount++;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// A notApplicable result may carry one scan-describing occurrence
|
|
180
|
+
// (empty selector) — never an element finding.
|
|
181
|
+
if (outcome === 'notApplicable') {
|
|
182
|
+
inapplicableRuleCount++;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// fail or cantTell: convert occurrences to flat findings, honoring
|
|
186
|
+
// per-occurrence grading when the rule tiered its findings.
|
|
187
|
+
(occurrences || []).forEach(occurrence => {
|
|
188
|
+
const tier = occurrence.occurrenceOutcome || outcome;
|
|
189
|
+
const cssPath = occurrence.selector || '';
|
|
190
|
+
let xPath = '';
|
|
191
|
+
try {
|
|
192
|
+
const element = cssPath && document.querySelector(cssPath);
|
|
193
|
+
if (element && typeof window.getXPath === 'function') {
|
|
194
|
+
xPath = window.getXPath(element) ?? '';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
// Unresolvable path: instance falls back to excerpt only.
|
|
199
|
+
}
|
|
200
|
+
// Reason for uncertainty: per occurrence, else per check.
|
|
201
|
+
const uncertainty = occurrence.uncertainty || check.uncertainty || {};
|
|
202
|
+
const finding = {
|
|
203
|
+
ruleID: ruleId || '',
|
|
204
|
+
what: (occurrence.summary || check.title || '').replace(/\s+/g, ' ').trim(),
|
|
205
|
+
wcag: criterionOf(check.meta),
|
|
206
|
+
severity: (check.severity || 'moderate').toLowerCase(),
|
|
207
|
+
uncertainty: tier === 'cantTell' ? (uncertainty.code || '') : '',
|
|
208
|
+
needed: tier === 'cantTell' ? (uncertainty.needed || '') : '',
|
|
209
|
+
html: (occurrence.html || '').slice(0, 500),
|
|
210
|
+
cssPath,
|
|
211
|
+
xPath
|
|
212
|
+
};
|
|
213
|
+
(tier === 'fail' ? violations : incomplete).push(finding);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
resolve({
|
|
217
|
+
engine: sureReport.engine || {},
|
|
218
|
+
violations,
|
|
219
|
+
incomplete,
|
|
220
|
+
passRuleCount,
|
|
221
|
+
inapplicableRuleCount,
|
|
222
|
+
manualRuleCount,
|
|
223
|
+
manualOccurrenceCount
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
resolve({
|
|
228
|
+
prevented: true,
|
|
229
|
+
error: error.message
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
;
|
|
233
|
+
}), {
|
|
234
|
+
scriptNonce,
|
|
235
|
+
script,
|
|
236
|
+
contextSelector
|
|
237
|
+
});
|
|
238
|
+
const { nativeResult } = result;
|
|
239
|
+
// If the tool ran and rules were selected:
|
|
240
|
+
if (!nativeResult.prevented && act.rules) {
|
|
241
|
+
// Remove results of other rules.
|
|
242
|
+
const { rules } = act;
|
|
243
|
+
nativeResult.violations = (nativeResult.violations || [])
|
|
244
|
+
.filter(finding => rules.includes(finding.ruleID));
|
|
245
|
+
nativeResult.incomplete = (nativeResult.incomplete || [])
|
|
246
|
+
.filter(finding => rules.includes(finding.ruleID));
|
|
247
|
+
}
|
|
248
|
+
// If the tool was prevented from running:
|
|
249
|
+
if (nativeResult.prevented) {
|
|
250
|
+
data.prevented = true;
|
|
251
|
+
data.error = nativeResult.error;
|
|
252
|
+
}
|
|
253
|
+
// Otherwise, if a standard result is to be reported:
|
|
254
|
+
else if (standard) {
|
|
255
|
+
const { standardResult } = result;
|
|
256
|
+
// Record the tallied-only bucket sizes.
|
|
257
|
+
data.passRuleCount = nativeResult.passRuleCount;
|
|
258
|
+
data.inapplicableRuleCount = nativeResult.inapplicableRuleCount;
|
|
259
|
+
data.manualRuleCount = nativeResult.manualRuleCount;
|
|
260
|
+
data.manualOccurrenceCount = nativeResult.manualOccurrenceCount;
|
|
261
|
+
// For each certainty band:
|
|
262
|
+
const bands = [
|
|
263
|
+
[nativeResult.incomplete || [], 0, 'cantTell'],
|
|
264
|
+
[nativeResult.violations || [], 2, 'failed']
|
|
265
|
+
];
|
|
266
|
+
bands.forEach(([findings, baseSeverity, outcome]) => {
|
|
267
|
+
// For each native-result finding:
|
|
268
|
+
findings.forEach(finding => {
|
|
269
|
+
// Add a standard-result instance, forwarding only a known uncertainty code.
|
|
270
|
+
const { ruleID, what, severity, uncertainty, needed, xPath } = finding;
|
|
271
|
+
(0, standard_1.addInstance)(standardResult, {
|
|
272
|
+
ruleID,
|
|
273
|
+
what,
|
|
274
|
+
ordinalSeverity: baseSeverity + (severityWeights[severity] ?? 0),
|
|
275
|
+
outcome,
|
|
276
|
+
uncertainty: standard_1.UNCERTAINTY_CODES.includes(uncertainty) ? uncertainty : undefined,
|
|
277
|
+
needed,
|
|
278
|
+
catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
data,
|
|
285
|
+
result
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
exports.reporter = reporter;
|