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/testaro/focAndOp.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
© 2021–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,117 +9,112 @@
|
|
|
7
9
|
|
|
8
10
|
SPDX-License-Identifier: MIT
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.reporter = void 0;
|
|
14
|
+
const testaro_1 = require("../procs/testaro");
|
|
11
15
|
/*
|
|
12
16
|
focAndOp
|
|
13
17
|
Related to Tenon rule 190.
|
|
14
18
|
|
|
15
19
|
This test reports discrepancies between Tab-focusability and operability. The standard practice is to make focusable elements operable and operable elements focusable. If focusable elements are not operable, users are likely to be surprised that nothing happens when they try to operate such elements. Conversely, if operable elements are not focusable, users who navigate with a keyboard are prevented from operating those elements. The test considers an element Tab-focusable if its tabIndex property has the value 0. The test considers an element operable if it has a non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name, has an interactive explicit role, or has an 'onclick' attribute.
|
|
20
|
+
Compiled to focAndOp.js by tsc (issue #73); edit this file, not the emitted one.
|
|
16
21
|
*/
|
|
17
|
-
|
|
18
|
-
// IMPORTS
|
|
19
|
-
|
|
20
|
-
const {doTest} = require('../procs/testaro');
|
|
21
|
-
|
|
22
22
|
// FUNCTIONS
|
|
23
|
-
|
|
24
23
|
// Runs the test and returns the result.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
24
|
+
const reporter = async (page, report, _, withItems) => {
|
|
25
|
+
// The candidate selector yields rendered HTML elements.
|
|
26
|
+
const getBadWhat = (element) => {
|
|
27
|
+
// Get whether the element is visible.
|
|
28
|
+
const isVisible = element.checkVisibility({
|
|
29
|
+
contentVisibilityAuto: true,
|
|
30
|
+
opacityProperty: true,
|
|
31
|
+
visibilityProperty: true
|
|
32
|
+
});
|
|
33
|
+
// If so:
|
|
34
|
+
if (isVisible) {
|
|
35
|
+
// Get whether it is focusable.
|
|
36
|
+
const isFocusable = element.tabIndex === 0;
|
|
37
|
+
// Get the operable tagnames.
|
|
38
|
+
const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT', 'OPTION', 'SELECT', 'SUMMARY', 'TEXTAREA']);
|
|
39
|
+
// Get the operable roles.
|
|
40
|
+
const opRoles = new Set([
|
|
41
|
+
'button',
|
|
42
|
+
'checkbox',
|
|
43
|
+
'combobox',
|
|
44
|
+
'composite',
|
|
45
|
+
'grid',
|
|
46
|
+
'gridcell',
|
|
47
|
+
'input',
|
|
48
|
+
'link',
|
|
49
|
+
'listbox',
|
|
50
|
+
'menu',
|
|
51
|
+
'menubar',
|
|
52
|
+
'menuitem',
|
|
53
|
+
'menuitemcheckbox',
|
|
54
|
+
'option',
|
|
55
|
+
'radio',
|
|
56
|
+
'radiogroup',
|
|
57
|
+
'scrollbar',
|
|
58
|
+
'searchbox',
|
|
59
|
+
'select',
|
|
60
|
+
'slider',
|
|
61
|
+
'spinbutton',
|
|
62
|
+
'switch',
|
|
63
|
+
'tab',
|
|
64
|
+
'tablist',
|
|
65
|
+
'textbox',
|
|
66
|
+
'tree',
|
|
67
|
+
'treegrid',
|
|
68
|
+
'treeitem',
|
|
69
|
+
'widget',
|
|
70
|
+
]);
|
|
71
|
+
// Initialize the operabilities of the element.
|
|
72
|
+
const opHow = [];
|
|
73
|
+
// If the element is not a label:
|
|
74
|
+
if (element.tagName !== 'LABEL') {
|
|
75
|
+
const liveStyleDec = window.getComputedStyle(element);
|
|
76
|
+
// If it has a pointer cursor:
|
|
77
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
78
|
+
// Neutralize the cursor style of the parent element of the element.
|
|
79
|
+
element.parentElement.style.cursor = 'default';
|
|
80
|
+
// If, after this, the element still has a pointer cursor:
|
|
81
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
82
|
+
// Add this to the operabilities of the element.
|
|
83
|
+
opHow.push('pointer cursor');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// If the element has a click event listener:
|
|
88
|
+
if (element.onclick) {
|
|
89
|
+
// Add this to the operabilities.
|
|
90
|
+
opHow.push('click listener');
|
|
91
|
+
}
|
|
92
|
+
// If the element has an operable explicit role:
|
|
93
|
+
const role = element.getAttribute('role');
|
|
94
|
+
if (opRoles.has(role)) {
|
|
95
|
+
// Add this to the operabilities.
|
|
96
|
+
opHow.push(`role ${role}`);
|
|
97
|
+
}
|
|
98
|
+
// If the element has an operable tagname:
|
|
99
|
+
const tagName = element.tagName;
|
|
100
|
+
if (opTags.has(tagName)) {
|
|
101
|
+
// Add this to the operabilities.
|
|
102
|
+
opHow.push(`tagname ${tagName}`);
|
|
103
|
+
}
|
|
104
|
+
const isOperable = opHow.length > 0;
|
|
105
|
+
// If the element is focusable but not operable:
|
|
106
|
+
if (isFocusable && !isOperable) {
|
|
107
|
+
// Return a severity and violation description.
|
|
108
|
+
return '2:Element is Tab-focusable but not operable';
|
|
109
|
+
}
|
|
110
|
+
// Otherwise, if it is operable but not focusable:
|
|
111
|
+
else if (isOperable && !isFocusable) {
|
|
112
|
+
// Return a severity and violation description.
|
|
113
|
+
return `3:Element is operable (${opHow.join(', ')}) but not Tab-focusable`;
|
|
114
|
+
}
|
|
87
115
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Add this to the operabilities.
|
|
92
|
-
opHow.push('click listener');
|
|
93
|
-
}
|
|
94
|
-
// If the element has an operable explicit role:
|
|
95
|
-
const role = element.getAttribute('role');
|
|
96
|
-
if (opRoles.has(role)) {
|
|
97
|
-
// Add this to the operabilities.
|
|
98
|
-
opHow.push(`role ${role}`);
|
|
99
|
-
}
|
|
100
|
-
// If the element has an operable tagname:
|
|
101
|
-
const tagName = element.tagName;
|
|
102
|
-
if (opTags.has(tagName)) {
|
|
103
|
-
// Add this to the operabilities.
|
|
104
|
-
opHow.push(`tagname ${tagName}`);
|
|
105
|
-
}
|
|
106
|
-
const isOperable = opHow.length > 0;
|
|
107
|
-
// If the element is focusable but not operable:
|
|
108
|
-
if (isFocusable && ! isOperable) {
|
|
109
|
-
// Return a severity and violation description.
|
|
110
|
-
return '2:Element is Tab-focusable but not operable';
|
|
111
|
-
}
|
|
112
|
-
// Otherwise, if it is operable but not focusable:
|
|
113
|
-
else if (isOperable && ! isFocusable) {
|
|
114
|
-
// Return a severity and violation description.
|
|
115
|
-
return `3:Element is operable (${opHow.join(', ')}) but not Tab-focusable`;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const whats = 'Elements are Tab-focusable but not operable or vice versa';
|
|
120
|
-
return await doTest(
|
|
121
|
-
page, report, withItems, 'focAndOp', 'body, body *', whats, 2, getBadWhat.toString()
|
|
122
|
-
);
|
|
116
|
+
};
|
|
117
|
+
const whats = 'Elements are Tab-focusable but not operable or vice versa';
|
|
118
|
+
return await (0, testaro_1.doTest)(page, report, withItems, 'focAndOp', 'body, body *', whats, 2, getBadWhat.toString());
|
|
123
119
|
};
|
|
120
|
+
exports.reporter = reporter;
|
|
@@ -0,0 +1,128 @@
|
|
|
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 {doTest} from '../procs/testaro';
|
|
16
|
+
import type {BadWhat, Report} from '../types';
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
focAndOp
|
|
20
|
+
Related to Tenon rule 190.
|
|
21
|
+
|
|
22
|
+
This test reports discrepancies between Tab-focusability and operability. The standard practice is to make focusable elements operable and operable elements focusable. If focusable elements are not operable, users are likely to be surprised that nothing happens when they try to operate such elements. Conversely, if operable elements are not focusable, users who navigate with a keyboard are prevented from operating those elements. The test considers an element Tab-focusable if its tabIndex property has the value 0. The test considers an element operable if it has a non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name, has an interactive explicit role, or has an 'onclick' attribute.
|
|
23
|
+
Compiled to focAndOp.js by tsc (issue #73); edit this file, not the emitted one.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// FUNCTIONS
|
|
27
|
+
|
|
28
|
+
// Runs the test and returns the result.
|
|
29
|
+
export const reporter = async (page: Page, report: Report, _: unknown, withItems: boolean) => {
|
|
30
|
+
// The candidate selector yields rendered HTML elements.
|
|
31
|
+
const getBadWhat = (element: HTMLElement): BadWhat => {
|
|
32
|
+
// Get whether the element is visible.
|
|
33
|
+
const isVisible = element.checkVisibility({
|
|
34
|
+
contentVisibilityAuto: true,
|
|
35
|
+
opacityProperty: true,
|
|
36
|
+
visibilityProperty: true
|
|
37
|
+
});
|
|
38
|
+
// If so:
|
|
39
|
+
if (isVisible) {
|
|
40
|
+
// Get whether it is focusable.
|
|
41
|
+
const isFocusable = element.tabIndex === 0;
|
|
42
|
+
// Get the operable tagnames.
|
|
43
|
+
const opTags = new Set(
|
|
44
|
+
['A', 'BUTTON', 'IFRAME', 'INPUT','OPTION', 'SELECT', 'SUMMARY', 'TEXTAREA']
|
|
45
|
+
);
|
|
46
|
+
// Get the operable roles.
|
|
47
|
+
const opRoles = new Set([
|
|
48
|
+
'button',
|
|
49
|
+
'checkbox',
|
|
50
|
+
'combobox',
|
|
51
|
+
'composite',
|
|
52
|
+
'grid',
|
|
53
|
+
'gridcell',
|
|
54
|
+
'input',
|
|
55
|
+
'link',
|
|
56
|
+
'listbox',
|
|
57
|
+
'menu',
|
|
58
|
+
'menubar',
|
|
59
|
+
'menuitem',
|
|
60
|
+
'menuitemcheckbox',
|
|
61
|
+
'option',
|
|
62
|
+
'radio',
|
|
63
|
+
'radiogroup',
|
|
64
|
+
'scrollbar',
|
|
65
|
+
'searchbox',
|
|
66
|
+
'select',
|
|
67
|
+
'slider',
|
|
68
|
+
'spinbutton',
|
|
69
|
+
'switch',
|
|
70
|
+
'tab',
|
|
71
|
+
'tablist',
|
|
72
|
+
'textbox',
|
|
73
|
+
'tree',
|
|
74
|
+
'treegrid',
|
|
75
|
+
'treeitem',
|
|
76
|
+
'widget',
|
|
77
|
+
]);
|
|
78
|
+
// Initialize the operabilities of the element.
|
|
79
|
+
const opHow = [];
|
|
80
|
+
// If the element is not a label:
|
|
81
|
+
if (element.tagName !== 'LABEL') {
|
|
82
|
+
const liveStyleDec = window.getComputedStyle(element);
|
|
83
|
+
// If it has a pointer cursor:
|
|
84
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
85
|
+
// Neutralize the cursor style of the parent element of the element.
|
|
86
|
+
element.parentElement!.style.cursor = 'default';
|
|
87
|
+
// If, after this, the element still has a pointer cursor:
|
|
88
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
89
|
+
// Add this to the operabilities of the element.
|
|
90
|
+
opHow.push('pointer cursor');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// If the element has a click event listener:
|
|
95
|
+
if (element.onclick) {
|
|
96
|
+
// Add this to the operabilities.
|
|
97
|
+
opHow.push('click listener');
|
|
98
|
+
}
|
|
99
|
+
// If the element has an operable explicit role:
|
|
100
|
+
const role = element.getAttribute('role');
|
|
101
|
+
if (opRoles.has(role as string)) {
|
|
102
|
+
// Add this to the operabilities.
|
|
103
|
+
opHow.push(`role ${role}`);
|
|
104
|
+
}
|
|
105
|
+
// If the element has an operable tagname:
|
|
106
|
+
const tagName = element.tagName;
|
|
107
|
+
if (opTags.has(tagName)) {
|
|
108
|
+
// Add this to the operabilities.
|
|
109
|
+
opHow.push(`tagname ${tagName}`);
|
|
110
|
+
}
|
|
111
|
+
const isOperable = opHow.length > 0;
|
|
112
|
+
// If the element is focusable but not operable:
|
|
113
|
+
if (isFocusable && ! isOperable) {
|
|
114
|
+
// Return a severity and violation description.
|
|
115
|
+
return '2:Element is Tab-focusable but not operable';
|
|
116
|
+
}
|
|
117
|
+
// Otherwise, if it is operable but not focusable:
|
|
118
|
+
else if (isOperable && ! isFocusable) {
|
|
119
|
+
// Return a severity and violation description.
|
|
120
|
+
return `3:Element is operable (${opHow.join(', ')}) but not Tab-focusable`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const whats = 'Elements are Tab-focusable but not operable or vice versa';
|
|
125
|
+
return await doTest(
|
|
126
|
+
page, report, withItems, 'focAndOp', 'body, body *', whats, 2, getBadWhat.toString()
|
|
127
|
+
);
|
|
128
|
+
};
|
package/testaro/focInd.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
© 2021–2023 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,7 +9,9 @@
|
|
|
7
9
|
|
|
8
10
|
SPDX-License-Identifier: MIT
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.reporter = void 0;
|
|
14
|
+
const testaro_1 = require("../procs/testaro");
|
|
11
15
|
/*
|
|
12
16
|
focInd
|
|
13
17
|
|
|
@@ -18,74 +22,69 @@
|
|
|
18
22
|
Solid outlines are the standard and thus most familiar focus indicator. Other focus indicators are likely to be misunderstood. For example, underlines may be mistaken for selection or link indicators.
|
|
19
23
|
|
|
20
24
|
WARNING: This test fails to recognize outlines when run with firefox.
|
|
25
|
+
Compiled to focInd.js by tsc (issue #73); edit this file, not the emitted one.
|
|
21
26
|
*/
|
|
22
|
-
|
|
23
|
-
// IMPORTS
|
|
24
|
-
|
|
25
|
-
const {doTest} = require('../procs/testaro');
|
|
26
|
-
|
|
27
27
|
// FUNCTIONS
|
|
28
|
-
|
|
29
28
|
// Runs the test and returns the result.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
29
|
+
const reporter = async (page, report, _, withItems) => {
|
|
30
|
+
// The candidate selector yields rendered HTML elements.
|
|
31
|
+
const getBadWhat = (element) => {
|
|
32
|
+
// Get whether the element is visible.
|
|
33
|
+
const isVisible = element.checkVisibility({
|
|
34
|
+
contentVisibilityAuto: true,
|
|
35
|
+
opacityProperty: true,
|
|
36
|
+
visibilityProperty: true
|
|
37
|
+
});
|
|
38
|
+
// If so:
|
|
39
|
+
if (isVisible) {
|
|
40
|
+
// Get whether it is focusable.
|
|
41
|
+
const isFocusable = element.tabIndex === 0;
|
|
42
|
+
// If so:
|
|
43
|
+
if (isFocusable) {
|
|
44
|
+
// Get its live style declaration.
|
|
45
|
+
const styleDec = window.getComputedStyle(element);
|
|
46
|
+
// If the element has a visible outline before being focused:
|
|
47
|
+
if (styleDec.outlineStyle !== 'none' && styleDec.outlineWidth !== '0px') {
|
|
48
|
+
// Return a violation description.
|
|
49
|
+
return 'Element is focusable but has an outline when blurred';
|
|
50
|
+
}
|
|
51
|
+
// Otherwise, i.e. if the element has no outline, focus the element.
|
|
52
|
+
element.focus({ preventScroll: true });
|
|
53
|
+
// If it now has no outline:
|
|
54
|
+
if (styleDec.outlineWidth === '0px' || styleDec.outlineStyle === 'none') {
|
|
55
|
+
// Return a violation description.
|
|
56
|
+
return 'Element when focused has no outline';
|
|
57
|
+
}
|
|
58
|
+
// Otherwise, if it now has an outline thinner than 2 pixels:
|
|
59
|
+
if (Number.parseFloat(styleDec.outlineWidth) < 2 && styleDec.outlineStyle !== 'none') {
|
|
60
|
+
// Return a violation description.
|
|
61
|
+
return 'Element when focused has an outline thinner than 2 pixels';
|
|
62
|
+
}
|
|
63
|
+
// Otherwise, if it now has a transparent outline:
|
|
64
|
+
if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
|
|
65
|
+
// Return a violation description.
|
|
66
|
+
return 'Element when focused has a transparent outline';
|
|
67
|
+
}
|
|
68
|
+
// Otherwise, if it now has a non-solid outline:
|
|
69
|
+
if (styleDec.outlineStyle !== 'solid') {
|
|
70
|
+
// If the outline style exists:
|
|
71
|
+
if (styleDec.outlineStyle) {
|
|
72
|
+
// If the style is not delegated to the user agent:
|
|
73
|
+
if (styleDec.outlineStyle !== 'auto') {
|
|
74
|
+
// Return a violation description
|
|
75
|
+
return `Element when focused has an outline with the ${styleDec.outlineStyle} instead of solid style`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Otherwise, i.e. if no outline style exists:
|
|
79
|
+
else {
|
|
80
|
+
// Return a violation description.
|
|
81
|
+
return 'Element when focused has an outline with no instead of solid style';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
|
-
}
|
|
78
|
-
// Otherwise, i.e. if no outline style exists:
|
|
79
|
-
else {
|
|
80
|
-
// Return a violation description.
|
|
81
|
-
return 'Element when focused has an outline with no instead of solid style';
|
|
82
|
-
}
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const whats = 'Elements fail to have standard focus indicators';
|
|
88
|
-
return await doTest(
|
|
89
|
-
page, report, withItems, 'focInd', 'body, body *', whats, 1, getBadWhat.toString()
|
|
90
|
-
);
|
|
86
|
+
};
|
|
87
|
+
const whats = 'Elements fail to have standard focus indicators';
|
|
88
|
+
return await (0, testaro_1.doTest)(page, report, withItems, 'focInd', 'body, body *', whats, 1, getBadWhat.toString());
|
|
91
89
|
};
|
|
90
|
+
exports.reporter = reporter;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2021–2023 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 {doTest} from '../procs/testaro';
|
|
16
|
+
import type {BadWhat, Report} from '../types';
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
focInd
|
|
20
|
+
|
|
21
|
+
This test reports focusable elements without standard focus indicators. The standard focus indicator is deemed to be a solid outline with a line thickness of at least 2 pixels and a non-transparent color, and only if the element, when not focused, has no outline.
|
|
22
|
+
|
|
23
|
+
The focus indicator is checked immediately after the element is focused. Thus, a delayed focus indicator is ignored. Indication delayed is treated as indication denied. The bases for this treatment are that delayed indication interferes with rapid human or mechanized document consumption and also, if it must be respected, slows accessibility testing.
|
|
24
|
+
|
|
25
|
+
Solid outlines are the standard and thus most familiar focus indicator. Other focus indicators are likely to be misunderstood. For example, underlines may be mistaken for selection or link indicators.
|
|
26
|
+
|
|
27
|
+
WARNING: This test fails to recognize outlines when run with firefox.
|
|
28
|
+
Compiled to focInd.js by tsc (issue #73); edit this file, not the emitted one.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// FUNCTIONS
|
|
32
|
+
|
|
33
|
+
// Runs the test and returns the result.
|
|
34
|
+
export const reporter = async (page: Page, report: Report, _: unknown, withItems: boolean) => {
|
|
35
|
+
// The candidate selector yields rendered HTML elements.
|
|
36
|
+
const getBadWhat = (element: HTMLElement): BadWhat => {
|
|
37
|
+
// Get whether the element is visible.
|
|
38
|
+
const isVisible = element.checkVisibility({
|
|
39
|
+
contentVisibilityAuto: true,
|
|
40
|
+
opacityProperty: true,
|
|
41
|
+
visibilityProperty: true
|
|
42
|
+
});
|
|
43
|
+
// If so:
|
|
44
|
+
if (isVisible) {
|
|
45
|
+
// Get whether it is focusable.
|
|
46
|
+
const isFocusable = element.tabIndex === 0;
|
|
47
|
+
// If so:
|
|
48
|
+
if (isFocusable) {
|
|
49
|
+
// Get its live style declaration.
|
|
50
|
+
const styleDec = window.getComputedStyle(element);
|
|
51
|
+
// If the element has a visible outline before being focused:
|
|
52
|
+
if (styleDec.outlineStyle !== 'none' && styleDec.outlineWidth !== '0px') {
|
|
53
|
+
// Return a violation description.
|
|
54
|
+
return 'Element is focusable but has an outline when blurred';
|
|
55
|
+
}
|
|
56
|
+
// Otherwise, i.e. if the element has no outline, focus the element.
|
|
57
|
+
element.focus({preventScroll: true});
|
|
58
|
+
// If it now has no outline:
|
|
59
|
+
if (styleDec.outlineWidth === '0px' || styleDec.outlineStyle === 'none') {
|
|
60
|
+
// Return a violation description.
|
|
61
|
+
return 'Element when focused has no outline';
|
|
62
|
+
}
|
|
63
|
+
// Otherwise, if it now has an outline thinner than 2 pixels:
|
|
64
|
+
if (Number.parseFloat(styleDec.outlineWidth) < 2 && styleDec.outlineStyle !== 'none') {
|
|
65
|
+
// Return a violation description.
|
|
66
|
+
return 'Element when focused has an outline thinner than 2 pixels';
|
|
67
|
+
}
|
|
68
|
+
// Otherwise, if it now has a transparent outline:
|
|
69
|
+
if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
|
|
70
|
+
// Return a violation description.
|
|
71
|
+
return 'Element when focused has a transparent outline';
|
|
72
|
+
}
|
|
73
|
+
// Otherwise, if it now has a non-solid outline:
|
|
74
|
+
if (styleDec.outlineStyle !== 'solid') {
|
|
75
|
+
// If the outline style exists:
|
|
76
|
+
if (styleDec.outlineStyle) {
|
|
77
|
+
// If the style is not delegated to the user agent:
|
|
78
|
+
if (styleDec.outlineStyle !== 'auto') {
|
|
79
|
+
// Return a violation description
|
|
80
|
+
return `Element when focused has an outline with the ${styleDec.outlineStyle} instead of solid style`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Otherwise, i.e. if no outline style exists:
|
|
84
|
+
else {
|
|
85
|
+
// Return a violation description.
|
|
86
|
+
return 'Element when focused has an outline with no instead of solid style';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const whats = 'Elements fail to have standard focus indicators';
|
|
93
|
+
return await doTest(
|
|
94
|
+
page, report, withItems, 'focInd', 'body, body *', whats, 1, getBadWhat.toString()
|
|
95
|
+
);
|
|
96
|
+
};
|