testaro 78.0.6 → 78.4.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 +49 -0
- package/.github/workflows/typescript.yml +35 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +25 -19
- package/CONTAINERS.md +10 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +75 -20
- package/UPGRADES.md +4 -0
- package/actSpecs-doc.md +13 -1
- package/actSpecs.js +29 -5
- package/call.js +6 -5
- package/docker-compose.yml +8 -2
- package/docs/checkpoint-scanning.md +199 -0
- package/docs/standard-result-outcome.md +165 -0
- package/env.example +44 -10
- package/eslint.config.mjs +101 -0
- package/netWatch.js +373 -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 +313 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +35 -0
- package/procs/config.js +63 -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/getSource.js +1 -0
- package/procs/job.js +116 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +239 -108
- package/procs/nu.d.ts +48 -0
- package/procs/nu.js +1 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- package/procs/shoot.js +3 -1
- 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 +237 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +82 -64
- 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 +39 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +32 -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 +30 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +31 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +28 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +21 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +60 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +33 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +318 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +18 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +34 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +58 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +26 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +114 -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 +20 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +186 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +101 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +65 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +29 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +62 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +55 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +164 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +156 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +15 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +18 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +32 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +18 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +44 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +80 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +14 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +26 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +23 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +31 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +42 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +93 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +40 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +17 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +19 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +37 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +58 -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 +28 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +31 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +249 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +273 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +131 -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 +26 -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 +16 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +20 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +139 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +273 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +200 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +142 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +175 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +52 -0
- package/tests/ibm.js +165 -166
- package/tests/ibm.ts +251 -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 +142 -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 +303 -272
- package/tests/qualWeb.ts +415 -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 +746 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +167 -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 +424 -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 +62 -10
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- package/memory/MEMORY.md +0 -3
- package/memory/project_validation_pause.md +0 -10
- 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/axe.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2021–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 type {AxeResults, NodeResult, RunOptions} from 'axe-core';
|
|
16
|
+
import {getAttributeXPath, getXPathCatalogIndex} from '../procs/xPath';
|
|
17
|
+
import {getStandardResult, addInstance} from '../procs/standard';
|
|
18
|
+
import type {Act, Report, StandardInstance, StandardResult} from '../types';
|
|
19
|
+
const axePlaywright = require('axe-playwright') as typeof import('axe-playwright');
|
|
20
|
+
|
|
21
|
+
// TYPES
|
|
22
|
+
|
|
23
|
+
// The axe-act properties this reporter consumes.
|
|
24
|
+
interface AxeAct extends Act {
|
|
25
|
+
detailLevel?: number;
|
|
26
|
+
rules?: string[];
|
|
27
|
+
}
|
|
28
|
+
// The impact-classified severity of an axe node.
|
|
29
|
+
type AxeImpact = 'minor' | 'moderate' | 'serious' | 'critical';
|
|
30
|
+
// The native result: rule and node totals and the axe report details.
|
|
31
|
+
interface AxeNativeResult {
|
|
32
|
+
totals?: {
|
|
33
|
+
rulesNA: number;
|
|
34
|
+
rulesPassed: number;
|
|
35
|
+
rulesWarned: number;
|
|
36
|
+
rulesViolated: number;
|
|
37
|
+
warnings: Record<AxeImpact, number>;
|
|
38
|
+
violations: Record<AxeImpact, number>;
|
|
39
|
+
};
|
|
40
|
+
details?: AxeResults;
|
|
41
|
+
}
|
|
42
|
+
// The data of the act report.
|
|
43
|
+
interface AxeData {
|
|
44
|
+
prevented?: boolean;
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CONSTANTS
|
|
49
|
+
|
|
50
|
+
const {injectAxe} = axePlaywright;
|
|
51
|
+
const severityWeights: Record<AxeImpact, number> = {
|
|
52
|
+
minor: 0,
|
|
53
|
+
moderate: 0,
|
|
54
|
+
serious: 1,
|
|
55
|
+
critical: 1
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
axe
|
|
60
|
+
Implements the axe-core ruleset for accessibility.
|
|
61
|
+
|
|
62
|
+
The rules argument defaults to all rules; otherwise, specify an array of rule names.
|
|
63
|
+
|
|
64
|
+
The detailLevel argument specifies how many result categories are to be included in the
|
|
65
|
+
details. 0 = none; 1 = violations; 2 = violations and incomplete; 3 = violations, incomplete,
|
|
66
|
+
and passes; 4 = violations, incomplete, passes, and inapplicable. Regardless of the value of this
|
|
67
|
+
argument, Axe-core is instructed to report all nodes with violation or incomplete results, but
|
|
68
|
+
only 1 node per rule found to be passed or inapplicable. Therefore, from the results of this test
|
|
69
|
+
it is possible to count the rules passed and the inapplicable rules, but not the nodes for which
|
|
70
|
+
each rule is passed or inapplicable. To count those nodes, one would need to revise the
|
|
71
|
+
'resultTypes' property of the 'axeOptions' object.
|
|
72
|
+
|
|
73
|
+
The report of this test shows rule totals by result category and, within the violation and
|
|
74
|
+
incomplete categories, node totals by severity. It does not show rule or node totals by test
|
|
75
|
+
category (“tag”), such as 'wcag21aaa'. Scoring can consider test categories by getting the value
|
|
76
|
+
of the 'tags' property of each rule.
|
|
77
|
+
Compiled to axe.js by tsc (issue #73); edit this file, not the emitted one.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// FUNCTIONS
|
|
81
|
+
|
|
82
|
+
// Conducts and reports the Axe tests.
|
|
83
|
+
export const reporter = async (page: Page, report: Report, actIndex: number) => {
|
|
84
|
+
const act = report.acts[actIndex] as AxeAct;
|
|
85
|
+
const {detailLevel, rules} = act;
|
|
86
|
+
// Initialize the act report.
|
|
87
|
+
let data: AxeData = {};
|
|
88
|
+
const result: {nativeResult: AxeNativeResult; standardResult: StandardResult} = {
|
|
89
|
+
nativeResult: {},
|
|
90
|
+
standardResult: {}
|
|
91
|
+
};
|
|
92
|
+
const standard = report.standard !== 'no';
|
|
93
|
+
// If standard results are to be reported:
|
|
94
|
+
if (standard) {
|
|
95
|
+
// Initialize the standard result.
|
|
96
|
+
result.standardResult = getStandardResult();
|
|
97
|
+
}
|
|
98
|
+
const {nativeResult, standardResult} = result;
|
|
99
|
+
// Inject axe-core into the page.
|
|
100
|
+
await injectAxe(page as Parameters<typeof injectAxe>[0])
|
|
101
|
+
.catch(error => {
|
|
102
|
+
console.log(`ERROR: Axe injection failed (${error.message})`);
|
|
103
|
+
data.prevented = true;
|
|
104
|
+
data.error = 'ERROR: axe injection failed';
|
|
105
|
+
});
|
|
106
|
+
// If the injection succeeded:
|
|
107
|
+
if (! data.prevented) {
|
|
108
|
+
// Get the data on the elements violating the specified axe-core rules.
|
|
109
|
+
const axeOptions: RunOptions = {
|
|
110
|
+
resultTypes: ['violations', 'incomplete']
|
|
111
|
+
};
|
|
112
|
+
if (rules && rules.length) {
|
|
113
|
+
// A bare array would be read as tag names; rule IDs need the object form.
|
|
114
|
+
axeOptions.runOnly = {type: 'rule', values: rules};
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
axeOptions.runOnly = ['experimental', 'best-practice', 'wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa'];
|
|
118
|
+
}
|
|
119
|
+
// If the act is scoped to changed subtrees, test only them (axe's include context).
|
|
120
|
+
const scopeRoots = report.scope?.roots;
|
|
121
|
+
const context = scopeRoots && scopeRoots.length
|
|
122
|
+
? {include: scopeRoots} as unknown as Parameters<typeof axePlaywright.getAxeResults>[1]
|
|
123
|
+
: null as unknown as undefined;
|
|
124
|
+
const axeReport = await axePlaywright.getAxeResults(
|
|
125
|
+
page as Parameters<typeof injectAxe>[0], context, axeOptions
|
|
126
|
+
);
|
|
127
|
+
const {inapplicable, passes, incomplete, violations} = axeReport;
|
|
128
|
+
// If the test succeeded:
|
|
129
|
+
if (violations) {
|
|
130
|
+
// Initialize the native result.
|
|
131
|
+
nativeResult.totals = {
|
|
132
|
+
rulesNA: 0,
|
|
133
|
+
rulesPassed: 0,
|
|
134
|
+
rulesWarned: 0,
|
|
135
|
+
rulesViolated: 0,
|
|
136
|
+
warnings: {
|
|
137
|
+
minor: 0,
|
|
138
|
+
moderate: 0,
|
|
139
|
+
serious: 0,
|
|
140
|
+
critical: 0
|
|
141
|
+
},
|
|
142
|
+
violations: {
|
|
143
|
+
minor: 0,
|
|
144
|
+
moderate: 0,
|
|
145
|
+
serious: 0,
|
|
146
|
+
critical: 0
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
nativeResult.details = axeReport;
|
|
150
|
+
// Populate the native-result totals.
|
|
151
|
+
const {totals} = nativeResult;
|
|
152
|
+
totals.rulesNA = inapplicable.length;
|
|
153
|
+
totals.rulesPassed = passes.length;
|
|
154
|
+
incomplete.forEach(rule => {
|
|
155
|
+
totals.rulesWarned++;
|
|
156
|
+
rule.nodes.forEach(node => {
|
|
157
|
+
totals.warnings[node.impact as AxeImpact]++;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
violations.forEach(rule => {
|
|
161
|
+
totals.rulesViolated++;
|
|
162
|
+
rule.nodes.forEach(node => {
|
|
163
|
+
totals.violations[node.impact as AxeImpact]++;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
// If standard results are to be reported and there are any suspicions:
|
|
167
|
+
if (standard && (totals.rulesViolated || totals.rulesWarned)) {
|
|
168
|
+
// Resolve each suspected element's FULL data-xpath from the live DOM,
|
|
169
|
+
// keyed by its axe target. axe-core truncates node.html (~300 chars,
|
|
170
|
+
// appends '...'), so parsing data-xpath out of node.html yields a
|
|
171
|
+
// truncated XPath for elements with long attribute lists. node.target
|
|
172
|
+
// is a reliable CSS selector; read the injected data-xpath directly off
|
|
173
|
+
// the element instead. Falls back to the node.html parse on any failure.
|
|
174
|
+
const fullXPathByTargetKey: Record<string, string> = {};
|
|
175
|
+
try {
|
|
176
|
+
const suspectNodes = (['incomplete', 'violations'] as const)
|
|
177
|
+
.filter(certainty => nativeResult?.details?.[certainty])
|
|
178
|
+
.flatMap(certainty => nativeResult.details![certainty].flatMap(rule => rule.nodes));
|
|
179
|
+
const targets = suspectNodes.map(node => node.target as unknown as string | string[]);
|
|
180
|
+
const resolvedXPaths = await page.evaluate(targetList => targetList.map(target => {
|
|
181
|
+
try {
|
|
182
|
+
// axe target is an array of selectors (nested arrays for frames/
|
|
183
|
+
// shadow roots). Use the deepest plain-string selector for the
|
|
184
|
+
// common, non-framed case; skip otherwise.
|
|
185
|
+
const selector = Array.isArray(target)
|
|
186
|
+
? (typeof target[target.length - 1] === 'string' ? target[target.length - 1] : null)
|
|
187
|
+
: (typeof target === 'string' ? target : null);
|
|
188
|
+
if (! selector) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const element = document.querySelector(selector);
|
|
192
|
+
return element ? element.getAttribute('data-xpath') : null;
|
|
193
|
+
}
|
|
194
|
+
catch(error) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}), targets);
|
|
198
|
+
suspectNodes.forEach((node, index) => {
|
|
199
|
+
if (resolvedXPaths[index]) {
|
|
200
|
+
fullXPathByTargetKey[JSON.stringify(node.target)] = resolvedXPaths[index];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch(error) {
|
|
205
|
+
// Leave the map empty; every instance falls back to node.html below.
|
|
206
|
+
}
|
|
207
|
+
// For each certainty type:
|
|
208
|
+
(['incomplete', 'violations'] as const).forEach(certainty => {
|
|
209
|
+
// If there are any suspicions of this type:
|
|
210
|
+
if (nativeResult?.details?.[certainty]) {
|
|
211
|
+
// For each rule with any suspicions:
|
|
212
|
+
nativeResult.details[certainty].forEach(rule => {
|
|
213
|
+
// For each element suspected of violating the rule:
|
|
214
|
+
rule.nodes.forEach((node: NodeResult) => {
|
|
215
|
+
// Get descriptions of the rule.
|
|
216
|
+
const whatSet = new Set([
|
|
217
|
+
rule.help,
|
|
218
|
+
... node.any.map(anyItem => anyItem.message),
|
|
219
|
+
... node.all.map(allItem => allItem.message)
|
|
220
|
+
]);
|
|
221
|
+
// Get the ordinal severity of the suspicion.
|
|
222
|
+
const ordinalSeverity = severityWeights[node.impact as AxeImpact]
|
|
223
|
+
+ (certainty === 'violations' ? 2 : 0);
|
|
224
|
+
// Get the XPath of the suspected element from its data-xpath
|
|
225
|
+
// attribute. Prefer the full value resolved from the live DOM
|
|
226
|
+
// (above); fall back to parsing it out of axe's node.html,
|
|
227
|
+
// which axe truncates and can corrupt the XPath.
|
|
228
|
+
const xPath = fullXPathByTargetKey[JSON.stringify(node.target)]
|
|
229
|
+
|| getAttributeXPath(node.html);
|
|
230
|
+
addInstance(standardResult, {
|
|
231
|
+
ruleID: rule.id,
|
|
232
|
+
what: Array.from(whatSet.values()).join('; '),
|
|
233
|
+
ordinalSeverity,
|
|
234
|
+
outcome: certainty === 'violations' ? 'failed' : 'cantTell',
|
|
235
|
+
catalogIndex: getXPathCatalogIndex(report, xPath)
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// Delete the properties of the report details that the detail level excludes, after the
|
|
243
|
+
// standard result has been made from them, so the standard result does not depend on
|
|
244
|
+
// the detail level.
|
|
245
|
+
const irrelevants = ['inapplicable', 'passes', 'incomplete', 'violations']
|
|
246
|
+
.slice(0, 4 - (detailLevel as number));
|
|
247
|
+
irrelevants.forEach(irrelevant => {
|
|
248
|
+
delete (axeReport as unknown as Record<string, unknown>)[irrelevant];
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// Otherwise, i.e. if the test failed:
|
|
252
|
+
else {
|
|
253
|
+
// Report this.
|
|
254
|
+
data.prevented = true;
|
|
255
|
+
data.error = 'ERROR: Act failed';
|
|
256
|
+
if (standard) {
|
|
257
|
+
standardResult.prevented = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Return the result.
|
|
262
|
+
try {
|
|
263
|
+
JSON.stringify(data);
|
|
264
|
+
}
|
|
265
|
+
catch(error) {
|
|
266
|
+
const message = `ERROR: Axe result cannot be made JSON (${(error as Error).message})`;
|
|
267
|
+
console.log(message);
|
|
268
|
+
data = {
|
|
269
|
+
prevented: true,
|
|
270
|
+
error: message
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
data,
|
|
275
|
+
result
|
|
276
|
+
};
|
|
277
|
+
};
|
package/tests/ed11y.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { Report, StandardResult } from '../types';
|
|
3
|
+
interface Ed11yNativeInstance {
|
|
4
|
+
test: string;
|
|
5
|
+
content: string;
|
|
6
|
+
dismissalKey: string;
|
|
7
|
+
html: string;
|
|
8
|
+
xPath: string | null | undefined;
|
|
9
|
+
}
|
|
10
|
+
interface Ed11yNativeResult {
|
|
11
|
+
resultCount?: number;
|
|
12
|
+
errorCount?: number;
|
|
13
|
+
warningCount?: number;
|
|
14
|
+
results?: Ed11yNativeInstance[];
|
|
15
|
+
prevented?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const reporter: (page: Page, report: Report, actIndex: number) => Promise<{
|
|
19
|
+
data: {
|
|
20
|
+
prevented?: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
result: {
|
|
24
|
+
nativeResult: Ed11yNativeResult;
|
|
25
|
+
standardResult: StandardResult;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
export {};
|
package/tests/ed11y.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
© 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
4
|
+
© 2026 Jeff Witt.
|
|
3
5
|
© 2025–2026 Jonathan Robert Pool.
|
|
4
6
|
|
|
5
7
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
@@ -7,114 +9,155 @@
|
|
|
7
9
|
|
|
8
10
|
SPDX-License-Identifier: MIT
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.reporter = void 0;
|
|
47
|
+
// IMPORTS
|
|
48
|
+
const fs = __importStar(require("fs/promises"));
|
|
49
|
+
const xPath_1 = require("../procs/xPath");
|
|
50
|
+
const standard_1 = require("../procs/standard");
|
|
11
51
|
/*
|
|
12
52
|
ed11y
|
|
13
53
|
Implements the Editoria11y ruleset for accessibility.
|
|
54
|
+
Compiled to ed11y.js by tsc (issue #73); edit this file, not the emitted one.
|
|
14
55
|
*/
|
|
15
|
-
|
|
16
|
-
// IMPORTS
|
|
17
|
-
|
|
18
|
-
const fs = require('fs/promises');
|
|
19
|
-
const {getXPathCatalogIndex} = require('../procs/xPath');
|
|
20
|
-
|
|
21
56
|
// FUNCTIONS
|
|
22
|
-
|
|
23
57
|
// Performs and reports the Editoria11y tests.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
const standard = report.standard !== 'no';
|
|
36
|
-
// If standard results are to be reported:
|
|
37
|
-
if (standard) {
|
|
38
|
-
// Initialize the standard result.
|
|
39
|
-
result.standardResult = {
|
|
40
|
-
prevented: false,
|
|
41
|
-
totals: [0, 0, 0, 0],
|
|
42
|
-
instances: []
|
|
58
|
+
const reporter = async (page, report, actIndex) => {
|
|
59
|
+
// Get the nonce, if any.
|
|
60
|
+
const act = report.acts[actIndex];
|
|
61
|
+
const { jobData } = report;
|
|
62
|
+
const scriptNonce = (jobData && jobData.lastScriptNonce);
|
|
63
|
+
// Initialize the act report.
|
|
64
|
+
let data = {};
|
|
65
|
+
const result = {
|
|
66
|
+
nativeResult: {},
|
|
67
|
+
standardResult: {}
|
|
43
68
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const standard = report.standard !== 'no';
|
|
70
|
+
// If standard results are to be reported:
|
|
71
|
+
if (standard) {
|
|
72
|
+
// Initialize the standard result.
|
|
73
|
+
result.standardResult = (0, standard_1.getStandardResult)();
|
|
74
|
+
}
|
|
75
|
+
// Get the tool script.
|
|
76
|
+
const script = await fs.readFile(`${__dirname}/../ed11y/editoria11y.min.js`, 'utf8');
|
|
77
|
+
// Perform the specified tests and populate the native result.
|
|
78
|
+
result.nativeResult = await page.evaluate(args => new Promise(async (resolve) => {
|
|
79
|
+
const { scriptNonce, script, rulesToTest } = args;
|
|
80
|
+
// When the script has been executed, creating data in an Ed11y object:
|
|
81
|
+
document.addEventListener('ed11yResults', async () => {
|
|
82
|
+
let { results } = Ed11y;
|
|
83
|
+
// If rules were selected:
|
|
84
|
+
if (rulesToTest) {
|
|
85
|
+
// Remove results of other rules.
|
|
86
|
+
results = results.filter(result => rulesToTest.includes(result.test));
|
|
87
|
+
}
|
|
88
|
+
// Return the native result.
|
|
89
|
+
resolve({
|
|
90
|
+
resultCount: results.length,
|
|
91
|
+
errorCount: Ed11y.errorCount,
|
|
92
|
+
warningCount: Ed11y.warningCount,
|
|
93
|
+
results: results.map(result => ({
|
|
94
|
+
test: result.test,
|
|
95
|
+
content: result.content.replace(/\s+/g, ' ').trim(),
|
|
96
|
+
dismissalKey: result.dismissalKey,
|
|
97
|
+
html: result.element.outerHTML.slice(0, 500),
|
|
98
|
+
xPath: window.getXPath(result.element)
|
|
99
|
+
}))
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// Add the tool script to the page.
|
|
103
|
+
const toolScript = document.createElement('script');
|
|
104
|
+
if (scriptNonce) {
|
|
105
|
+
toolScript.nonce = scriptNonce;
|
|
106
|
+
console.log(`Added nonce ${scriptNonce} to tool script`);
|
|
107
|
+
}
|
|
108
|
+
toolScript.textContent = script;
|
|
109
|
+
document.body.insertAdjacentElement('beforeend', toolScript);
|
|
110
|
+
// Execute the tool script, creating Ed11y and triggering the event listener.
|
|
111
|
+
try {
|
|
112
|
+
await new Ed11y({
|
|
113
|
+
alertMode: 'headless'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
resolve({
|
|
118
|
+
prevented: true,
|
|
119
|
+
error: error.message
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
;
|
|
123
|
+
}), {
|
|
124
|
+
scriptNonce,
|
|
125
|
+
script,
|
|
126
|
+
rulesToTest: act.rules
|
|
71
127
|
});
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
128
|
+
// If the tool script failed to run:
|
|
129
|
+
if (result.nativeResult.prevented) {
|
|
130
|
+
// Report the prevention.
|
|
131
|
+
data.prevented = true;
|
|
132
|
+
data.error = result.nativeResult.error;
|
|
133
|
+
if (standard) {
|
|
134
|
+
result.standardResult.prevented = true;
|
|
135
|
+
}
|
|
77
136
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
137
|
+
// Otherwise, i.e. if it ran, and if a standard result is to be reported:
|
|
138
|
+
else if (standard) {
|
|
139
|
+
const { standardResult } = result;
|
|
140
|
+
const { warningCount, errorCount, results } = result.nativeResult;
|
|
141
|
+
// Populate the standard-result totals.
|
|
142
|
+
standardResult.totals = [warningCount, 0, errorCount, 0];
|
|
143
|
+
// For each native-result instance:
|
|
144
|
+
results.forEach(nativeInstance => {
|
|
145
|
+
// Create a standard-result instance.
|
|
146
|
+
const { test, content, dismissalKey, xPath } = nativeInstance;
|
|
147
|
+
// A dismissable warning is a manual check.
|
|
148
|
+
(0, standard_1.pushInstance)(standardResult, {
|
|
149
|
+
ruleID: test,
|
|
150
|
+
what: content,
|
|
151
|
+
ordinalSeverity: dismissalKey ? 0 : 2,
|
|
152
|
+
outcome: dismissalKey ? 'cantTell' : 'failed',
|
|
153
|
+
uncertainty: dismissalKey ? 'judgement-required' : undefined,
|
|
154
|
+
catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
|
|
155
|
+
});
|
|
156
|
+
});
|
|
85
157
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
error: error.message
|
|
90
|
-
});
|
|
158
|
+
return {
|
|
159
|
+
data,
|
|
160
|
+
result
|
|
91
161
|
};
|
|
92
|
-
}), {
|
|
93
|
-
scriptNonce,
|
|
94
|
-
script,
|
|
95
|
-
rulesToTest: act.rules
|
|
96
|
-
});
|
|
97
|
-
// If a standard result is to be reported:
|
|
98
|
-
if (standard) {
|
|
99
|
-
const {standardResult} = result;
|
|
100
|
-
const {warningCount, errorCount, results} = result.nativeResult;
|
|
101
|
-
// Populate the standard-result totals.
|
|
102
|
-
standardResult.totals = [warningCount, 0, errorCount, 0];
|
|
103
|
-
// For each native-result instance:
|
|
104
|
-
results.forEach(nativeInstance => {
|
|
105
|
-
// Create a standard-result instance.
|
|
106
|
-
const {test, content, dismissalKey, xPath} = nativeInstance;
|
|
107
|
-
const instance = {};
|
|
108
|
-
instance.ruleID = test;
|
|
109
|
-
instance.what = content;
|
|
110
|
-
instance.ordinalSeverity = dismissalKey ? 0 : 2;
|
|
111
|
-
instance.count = 1;
|
|
112
|
-
instance.catalogIndex = getXPathCatalogIndex(report, xPath);
|
|
113
|
-
standardResult.instances.push(instance);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
data,
|
|
118
|
-
result
|
|
119
|
-
};
|
|
120
162
|
};
|
|
163
|
+
exports.reporter = reporter;
|