testaro 78.0.8 → 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 +24 -18
- package/CONTAINERS.md +9 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +73 -19
- 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 +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 +35 -0
- package/procs/config.js +62 -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 +239 -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 +52 -0
- package/tests/ibm.js +164 -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 +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 +302 -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 +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 +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 +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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Stage-3b mapping proposals — pour
|
|
2
|
+
|
|
3
|
+
Capture: `validation/act/results/stage3a-2026-08-22.jsonl` · taxonomy: `tic50.js` · thresholds: rate ≥ 0.25, ≥ 5 elements on ≥ 3 pages. Exact-path co-occurrence is a floor; proposals require human accept/reject/split review.
|
|
4
|
+
|
|
5
|
+
| Candidate rule | Elements | Pages | Proposed issue | Co-rate | Lift | Runners-up | Suggestion | Verdict |
|
|
6
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
7
|
+
| color-contrast-enhanced | 12965 | 222 | contrastAAA | 43% (5611) | 4.5 | contrastAA (5058, lift 4.1); contrastRisk (1746, lift 3.0) | map | |
|
|
8
|
+
| target-size-enhanced | 8992 | 248 | — | 2% (171) | 5.1 | linkTextsSame (667, lift 3.4); newTabSurprise (565, lift 3.5) | new-issue candidate | |
|
|
9
|
+
| color-contrast | 8775 | 213 | contrastAA | 49% (4287) | 5.1 | contrastRisk (1639, lift 4.2); linkVaguenessRisk (27, lift 8.0) | map | |
|
|
10
|
+
| region | 8013 | 150 | — | 0% (28) | 9.0 | contentBeyondLandmarks (722, lift 3.1); imageNoText (141, lift 3.5) | new-issue candidate | |
|
|
11
|
+
| new-window-link | 1697 | 196 | newTabSurprise | 63% (1064) | 34.4 | nonWebLink (18, lift 20.4); linkIndication (31, lift 16.2) | map | |
|
|
12
|
+
| svg-img-alt | 818 | 87 | — | 24% (194) | 8.4 | svgImageNoText (1, lift 11.1); eventKeyboardRisk (2, lift 6.2) | new-issue candidate | |
|
|
13
|
+
| target-size | 678 | 58 | — | 1% (8) | 26.7 | ignorable (551, lift 1.2); linkConfusionRisk (8, lift 3.2) | new-issue candidate | |
|
|
14
|
+
| text-spacing | 580 | 61 | — | 2% (10) | 22.3 | dialogNoText (1, lift 31.2); inputNoText (1, lift 22.3) | new-issue candidate | |
|
|
15
|
+
| link-name | 536 | 88 | imageLinkNoText | 60% (319) | 115.4 | linkNoText (509, lift 91.1); newTabSurprise (127, lift 13.0) | map | |
|
|
16
|
+
| redundant-aria-label | 510 | 63 | — | 2% (9) | 53.3 | linkTextsSame (47, lift 4.3); ariaAttributeBad (16, lift 5.8) | new-issue candidate | |
|
|
17
|
+
| image-alt | 484 | 69 | imageNoText | 67% (322) | 133.9 | imageTextBad (31, lift 175.8); linkPair (7, lift 13.2) | map | |
|
|
18
|
+
| landmark-unique | 480 | 75 | bannerNotTop | 27% (131) | 177.9 | mainNotTop (111, lift 179.0); landmarkConfusion (91, lift 177.0) | map | |
|
|
19
|
+
| redundant-tabindex | 479 | 60 | — | 3% (16) | 10.9 | ariaAttributeBad (23, lift 8.9); visibleLabelNotInName (19, lift 4.7) | new-issue candidate | |
|
|
20
|
+
| redundant-aria | 337 | 61 | — | 15% (52) | 57.8 | activeEmbedding (8, lift 76.8); focusableDescendants (8, lift 21.7) | new-issue candidate | |
|
|
21
|
+
| landmark-top-level | 302 | 37 | bannerNotTop | 45% (135) | 291.3 | mainNotTop (112, lift 287.1); footerNotTop (6, lift 257.1) | map | |
|
|
22
|
+
| link-text-generic | 279 | 77 | — | 12% (33) | 306.1 | linkTextsSame (172, lift 28.5); linkConfusionRisk (36, lift 34.9) | new-issue candidate | |
|
|
23
|
+
| link-text-generic-only | 279 | 77 | — | 12% (33) | 306.1 | linkTextsSame (172, lift 28.5); linkConfusionRisk (36, lift 34.9) | new-issue candidate | |
|
|
24
|
+
| aria-hidden-focus | 231 | 27 | focusableHidden | 88% (204) | 377.4 | ignorable (120, lift 0.8); boxSizeAbsolute (5, lift 0.8) | map | |
|
|
25
|
+
| non-text-contrast | 190 | 90 | — | 3% (6) | 476.8 | labelConfusionRisk (86, lift 121.7); controlLabelInvisible (2, lift 476.8) | new-issue candidate | |
|
|
26
|
+
| nested-interactive | 183 | 29 | — | 14% (25) | 442.0 | focusableDescendants (20, lift 100.0); ariaAttributeBad (17, lift 17.1) | new-issue candidate | |
|
|
27
|
+
| aria-allowed-attr | 170 | 30 | ariaAttributeBad | 48% (81) | 87.9 | duplicateID (1, lift 5.5); landmarkConfusion (1, lift 5.5) | map | |
|
|
28
|
+
| focus-visible | 167 | 167 | — | 21% (35) | 287.7 | mainNone (48, lift 252.8); skipRepeatedContent (6, lift 191.4) | new-issue candidate | |
|
|
29
|
+
| aria-valid-refs | 167 | 36 | — | 22% (36) | 39.8 | activeEmbedding (4, lift 77.5); dialogNoText (1, lift 108.5) | new-issue candidate | |
|
|
30
|
+
| heading-order | 153 | 87 | headingLevelSkip | 95% (146) | 313.2 | headingEmpty (2, lift 16.9); headingLength (1, lift 20.4) | map | |
|
|
31
|
+
| redundant-role | 135 | 72 | — | 5% (7) | 48.4 | asideNotTop (3, lift 15.0); labelConfusionRisk (4, lift 8.0) | new-issue candidate | |
|
|
32
|
+
| landmark-one-main | 133 | 133 | mainNone | 77% (102) | 674.5 | pageLanguage (29, lift 564.3); h1Not1 (44, lift 454.1) | map | |
|
|
33
|
+
| redundant-image-alt | 120 | 19 | imageTextRedundant | 69% (83) | 482.0 | ignorable (74, lift 0.9) | map | |
|
|
34
|
+
| button-name | 111 | 32 | buttonNoText | 97% (108) | 318.2 | linkNoText (13, lift 11.2); ignorable (90, lift 1.2) | map | |
|
|
35
|
+
| focus-not-obscured | 108 | 8 | — | 16% (17) | 51.5 | newTabSurprise (16, lift 8.1); linkConfusionRisk (2, lift 5.0) | new-issue candidate | |
|
|
36
|
+
| control-contrast | 103 | 52 | labelConfusionRisk | 57% (59) | 154.0 | labelNot1 (4, lift 390.9); selectNoText (2, lift 293.2) | map | |
|
|
37
|
+
| form-label | 88 | 55 | — | 2% (2) | 1029.4 | selectNoText (4, lift 686.3); labelConfusionRisk (66, lift 201.6) | new-issue candidate | |
|
|
38
|
+
| redundant-alt-phrase | 87 | 27 | — | 1% (1) | 69.4 | ignorable (85, lift 1.4); contentBeyondLandmarks (7, lift 2.8) | new-issue candidate | |
|
|
39
|
+
| listitem-parent | 86 | 7 | listItemOrphan | 45% (39) | 708.3 | ignorable (2, lift 0.0) | map | |
|
|
40
|
+
| page-heading-one | 80 | 80 | h1Not1 | 71% (57) | 977.9 | pageTitle (5, lift 1132.3); skipRepeatedContent (10, lift 666.1) | map | |
|
|
41
|
+
| pause-stop-hide | 79 | 21 | — | 1% (1) | 11.8 | footerNot1 (1, lift 11.4); contrastAA (16, lift 2.1) | new-issue candidate | |
|
|
42
|
+
| aria-label-misuse | 77 | 16 | attributeBad | 95% (73) | 825.8 | ariaAttributeBad (69, lift 165.3); contentBeyondLandmarks (5, lift 2.3) | map | |
|
|
43
|
+
| link-in-text-block | 61 | 25 | linkIndication | 57% (35) | 509.6 | linkVaguenessRisk (2, lift 84.9); linkFileName (1, lift 40.1) | map | |
|
|
44
|
+
| empty-heading | 47 | 24 | headingEmpty | 100% (47) | 1294.1 | headingLevelSkip (3, lift 20.9); ignorable (36, lift 1.1) | map | |
|
|
45
|
+
| area-alt | 47 | 1 | — | 100% (47) | 1927.4 | linkNoText (47, lift 96.0); ignorable (47, lift 1.5) | low support | |
|
|
46
|
+
| meta-viewport | 38 | 37 | metaBansZoom | 95% (36) | 1751.4 | — | map | |
|
|
47
|
+
| aria-field-name | 38 | 7 | — | 82% (31) | 2383.9 | inputNoText (6, lift 2043.3); controlNoText (6, lift 185.8) | new-issue candidate | |
|
|
48
|
+
| media-captions | 38 | 4 | — | 21% (8) | 346.7 | videoNoTranscript (3, lift 255.4); videoNoAudioTrack (3, lift 255.4) | new-issue candidate | |
|
|
49
|
+
| scrollable-region-focusable | 32 | 12 | keyboardScroll | 28% (9) | 849.3 | contentBeyondLandmarks (1, lift 1.1) | map | |
|
|
50
|
+
| html-lang | 31 | 31 | pageLanguage | 100% (31) | 2588.2 | pageTitle (5, lift 2922.2); mainNone (26, lift 737.6) | map | |
|
|
51
|
+
| frame-title | 26 | 18 | iframeTitleBad | 73% (19) | 1085.2 | boxSizeAbsolute (4, lift 5.5); ignorable (19, lift 1.1) | map | |
|
|
52
|
+
| meta-refresh | 24 | 24 | refresh | 96% (23) | 3617.2 | — | map | |
|
|
53
|
+
| p-as-heading | 24 | 14 | — | 17% (4) | 112.7 | — | new-issue candidate | |
|
|
54
|
+
| definition-list | 24 | 1 | — | 100% (24) | 1.5 | — | low support | |
|
|
55
|
+
| video-loop-motion | 23 | 23 | videoNoAudioDescription | 83% (19) | 2078.7 | audioNoText (19, lift 1626.8); videoNoTranscript (13, lift 1828.6) | map | |
|
|
56
|
+
| list-structure | 20 | 12 | listChild | 95% (19) | 4098.0 | descendantMissing (13, lift 550.3); ignorable (19, lift 1.4) | map | |
|
|
57
|
+
| button-type | 17 | 10 | — | 12% (2) | 38.5 | visibleLabelNotInName (3, lift 21.1); ariaAttributeBad (1, lift 10.9) | new-issue candidate | |
|
|
58
|
+
| positive-tabindex | 14 | 7 | tabIndexPositive | 100% (14) | 6470.5 | skipRepeatedContent (1, lift 380.6); labelConfusionRisk (4, lift 76.8) | map | |
|
|
59
|
+
| reflow | 12 | 12 | h1Not1 | 42% (5) | 571.9 | mainNone (3, lift 219.9); iframeTitleBad (1, lift 123.8) | map | |
|
|
60
|
+
| aria-attr-valid | 9 | 4 | ariaAttributeBad | 100% (9) | 184.5 | ignorable (4, lift 0.7) | map | |
|
|
61
|
+
| canvas-alt | 8 | 5 | — | 50% (4) | 17.8 | ignorable (3, lift 0.6) | new-issue candidate | |
|
|
62
|
+
| no-autofocus | 8 | 7 | — | 25% (2) | 134.0 | labelConfusionRisk (3, lift 100.8); ignorable (8, lift 1.5) | new-issue candidate | |
|
|
63
|
+
| bypass-blocks | 6 | 6 | skipRepeatedContent | 100% (6) | 5328.6 | h1Not1 (3, lift 686.3); pageLanguage (2, lift 862.7) | map | |
|
|
64
|
+
| document-title | 6 | 6 | pageTitle | 83% (5) | 15097.8 | pageLanguage (5, lift 2156.8); h1Not1 (6, lift 1372.5) | map | |
|
|
65
|
+
| aria-required-children | 5 | 4 | — | 20% (1) | 2588.2 | descendantMissing (5, lift 846.6); controlNoText (1, lift 235.3) | new-issue candidate | |
|
|
66
|
+
| multiple-labels | 5 | 4 | labelNot1 | 100% (5) | 10065.2 | duplicateID (4, lift 747.1); ariaMissing (1, lift 862.7) | map | |
|
|
67
|
+
| autocomplete-valid | 5 | 3 | autocompleteBad | 100% (5) | 15097.8 | ignorable (5, lift 1.5); contrastAA (1, lift 2.1) | map | |
|
|
68
|
+
| dialog-name | 4 | 4 | — | 100% (4) | 18117.4 | ariaAttributeBad (1, lift 46.1) | low support | |
|
|
69
|
+
| fieldset-legend | 4 | 4 | — | 25% (1) | 2516.3 | controlNoText (1, lift 294.1); duplicateID (1, lift 233.5) | low support | |
|
|
70
|
+
| aria-required-parent | 4 | 1 | — | 0% (0) | 0.0 | — | low support | |
|
|
71
|
+
| label-for-valid | 3 | 3 | — | 33% (1) | 11.6 | contrastAA (2, lift 7.0); contrastAAA (2, lift 6.9) | low support | |
|
|
72
|
+
| table-headers | 3 | 3 | — | 100% (3) | 2264.7 | tableHeaderless (2, lift 1830.0); tabularTableless (2, lift 1776.2) | low support | |
|
|
73
|
+
| visual-order-divergence | 3 | 3 | — | 33% (1) | 3.4 | ignorable (1, lift 0.5) | low support | |
|
|
74
|
+
| auth-field-obstruction | 3 | 1 | — | 100% (3) | 1.5 | — | low support | |
|
|
75
|
+
| composite-widget-name | 2 | 2 | — | 50% (1) | 6470.5 | ariaAttributeBad (2, lift 184.5) | low support | |
|
|
76
|
+
| valid-role | 1 | 1 | — | 100% (1) | 536.0 | focusableRole (1, lift 374.3); ignorable (1, lift 1.5) | low support | |
|
|
77
|
+
|
|
78
|
+
Summary: 70 fired rules — 33 mapping proposals, 26 new-issue candidates, 11 low-support.
|
|
79
|
+
|
|
80
|
+
Incumbent instances with no tic mapping (top 10 — taxonomy coverage gaps):
|
|
81
|
+
- qualWeb:QW-BP30: 807
|
|
82
|
+
- ed11y:altURLLinked: 107
|
|
83
|
+
- axe:meta-refresh: 24
|
|
84
|
+
- ed11y:altMeaningless: 21
|
|
85
|
+
- ed11y:altMeaninglessLinked: 12
|
|
86
|
+
- axe:aria-tab-name: 1
|
|
87
|
+
|
|
88
|
+
## Reading notes for the triage pass
|
|
89
|
+
|
|
90
|
+
- **High-confidence accepts** (semantic match + dominant co-rate + high lift): empty-heading→headingEmpty, button-name→buttonNoText, heading-order→headingLevelSkip, landmark-one-main→mainNone, page-heading-one→h1Not1, image-alt→imageNoText, aria-hidden-focus→focusableHidden, new-window-link→newTabSurprise, document-title→pageTitle, bypass-blocks→skipRepeatedContent, autocomplete-valid→autocompleteBad, multiple-labels→labelNot1, aria-label-misuse→attributeBad, link-in-text-block→linkIndication, listitem-parent→listItemOrphan, redundant-image-alt→imageTextRedundant, color-contrast→contrastAA, color-contrast-enhanced→contrastAAA.
|
|
91
|
+
- **Split candidates**: link-name (linkNoText vs imageLinkNoText both co-fire strongly — tic distinguishes text links from image links; pour's one rule covers both); landmark-unique / landmark-top-level (bannerNotTop / mainNotTop / footerNotTop family).
|
|
92
|
+
- **Likely genuine pour novelty** (new-issue candidates with no plausible runner-up): text-spacing (1.4.12 — no incumbent equivalent fired anywhere), target-size / target-size-enhanced (2.5.8/2.5.5 — tic appears to lack target-size issues), non-text-contrast (1.4.11), focus-visible, focus-not-obscured (2.4.11), and the redundant-* best-practice family (pour's "overdoing it" rules).
|
|
93
|
+
- **Known ranking quirk**: extreme-lift/tiny-support pairs can outscore the right answer when a rule's true match has moderate lift (see form-label, where labelConfusionRisk — 66 elements, lift 202 — is the plausible mapping but a 2-element pair scored higher and then failed thresholds). The runners-up column carries the correction; this is why proposals are review inputs, not auto-accepts.
|
|
94
|
+
- **Byproduct — taxonomy coverage gaps** (incumbent rules with no tic home; upstream-filing material): qualWeb QW-BP30 (807 instances), ed11y altURLLinked/altMeaningless/altMeaninglessLinked, axe meta-refresh, axe aria-tab-name.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Stage-3b triage draft — pour (for human review)
|
|
2
|
+
|
|
3
|
+
Drafted 2026-09-01 by Claude from `stage3b-mapping-proposals.md` + the reading notes. Every
|
|
4
|
+
verdict below is a **draft for Jeff's accept/override** — semantic judgment calls are marked
|
|
5
|
+
⚠️ where the co-occurrence evidence and the rule semantics disagree (the known ranking quirk).
|
|
6
|
+
Review workflow: scan the ⚠️ rows first (9 of them), then rubber-stamp or override the rest.
|
|
7
|
+
Accepted verdicts become tic entries (initial `quality: 1`) in the next tic version.
|
|
8
|
+
|
|
9
|
+
## A. Mapping proposals — draft ACCEPT as proposed (24)
|
|
10
|
+
|
|
11
|
+
The 18 high-confidence accepts from the reading notes, plus 6 where semantics and evidence
|
|
12
|
+
agree cleanly:
|
|
13
|
+
|
|
14
|
+
| Pour rule | tic issue | Note |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| empty-heading | headingEmpty | 100% co-rate |
|
|
17
|
+
| button-name | buttonNoText | 97% |
|
|
18
|
+
| heading-order | headingLevelSkip | 95% |
|
|
19
|
+
| landmark-one-main | mainNone | 77% |
|
|
20
|
+
| page-heading-one | h1Not1 | 71% |
|
|
21
|
+
| image-alt | imageNoText | 67% |
|
|
22
|
+
| aria-hidden-focus | focusableHidden | 88% |
|
|
23
|
+
| new-window-link | newTabSurprise | 63% |
|
|
24
|
+
| document-title | pageTitle | 83% |
|
|
25
|
+
| bypass-blocks | skipRepeatedContent | 100% |
|
|
26
|
+
| autocomplete-valid | autocompleteBad | 100% |
|
|
27
|
+
| multiple-labels | labelNot1 | 100% |
|
|
28
|
+
| aria-label-misuse | attributeBad | 95% |
|
|
29
|
+
| link-in-text-block | linkIndication | 57% |
|
|
30
|
+
| listitem-parent | listItemOrphan | 45%, no plausible rival |
|
|
31
|
+
| redundant-image-alt | imageTextRedundant | 69% |
|
|
32
|
+
| color-contrast | contrastAA | 49% — plus contrastRisk co-fire is expected (bands) |
|
|
33
|
+
| color-contrast-enhanced | contrastAAA | 43% |
|
|
34
|
+
| aria-allowed-attr | ariaAttributeBad | 48%, semantic exact |
|
|
35
|
+
| meta-viewport | metaBansZoom | 95%, semantic exact |
|
|
36
|
+
| scrollable-region-focusable | keyboardScroll | 28% co-rate but semantic exact — incumbents fire rarely |
|
|
37
|
+
| html-lang | pageLanguage | 100% |
|
|
38
|
+
| frame-title | iframeTitleBad | 73% |
|
|
39
|
+
| meta-refresh | refresh | 96% — also fills the axe:meta-refresh coverage gap |
|
|
40
|
+
| list-structure | listChild | 95% |
|
|
41
|
+
| aria-attr-valid | ariaAttributeBad | 100% (9 el) |
|
|
42
|
+
| positive-tabindex | tabIndexPositive | 100% |
|
|
43
|
+
|
|
44
|
+
(27 rows — three of the "33 proposals" move to §B/§C below instead.)
|
|
45
|
+
|
|
46
|
+
## B. Mapping proposals — draft SPLIT (3)
|
|
47
|
+
|
|
48
|
+
| Pour rule | Draft | Rationale |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| link-name | **split → imageLinkNoText / linkNoText** by flagged-element content (contains img/svg → imageLinkNoText, else linkNoText) | Both co-fire strongly (60% / 509-element runner-up); tic distinguishes text links from image links, pour's one rule covers both. Split at mapping time on the instance's element, not the rule. |
|
|
51
|
+
| landmark-top-level | **split → bannerNotTop / mainNotTop / footerNotTop / asideNotTop** by landmark role | The tic *NotTop family is per-landmark; pour's one rule covers all. Runner-up evidence shows exactly this family. |
|
|
52
|
+
| landmark-unique | ⚠️ **remap → landmarkConfusion** | Proposed bannerNotTop (27%) is a co-occurrence artifact — landmark-unique is about *duplicate/undifferentiated* landmarks, which is landmarkConfusion's semantics (91-element runner-up, lift 177). |
|
|
53
|
+
|
|
54
|
+
## C. Mapping proposals — draft REJECT the proposed issue, re-disposition (3) — all ⚠️
|
|
55
|
+
|
|
56
|
+
| Pour rule | Proposed (rejected) | Draft disposition | Rationale |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| video-loop-motion | videoNoAudioDescription (83%) | **new issue** `motionNoControl` (2.2.2), shared with pause-stop-hide | Rule is about looping/auto-playing motion, not audio description — co-occurrence artifact (pages with video have both defects). |
|
|
59
|
+
| reflow | h1Not1 (42%) | **new issue** `reflowBroken` (1.4.10) | Semantic nonsense as a mapping; 1.4.10 has no incumbent coverage — this is novelty, mis-filed by base-rate. |
|
|
60
|
+
| control-contrast | labelConfusionRisk (57%) | **fold into** the `nonTextContrast` new issue (1.4.11) with non-text-contrast | 1.4.11 control contrast ≠ label confusion; co-occurrence artifact. |
|
|
61
|
+
|
|
62
|
+
## D. New-issue candidates — draft REMAP to an existing tic issue instead (8) — all ⚠️ except where noted
|
|
63
|
+
|
|
64
|
+
The ranking quirk in reverse: the right mapping existed but lost on lift/threshold.
|
|
65
|
+
|
|
66
|
+
| Pour rule | Draft map | Rationale |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| region | contentBeyondLandmarks | Semantic exact; 722-element runner-up (lift 3.1) lost on co-rate only because region fires far more broadly than the incumbent. |
|
|
69
|
+
| svg-img-alt | svgImageNoText | Semantic exact; incumbent fires so rarely (1 element) the co-rate is meaningless. |
|
|
70
|
+
| nested-interactive | focusableDescendants | Semantic exact (interactive inside interactive), lift 100. |
|
|
71
|
+
| aria-required-children | descendantMissing | Semantic exact (role's required owned children absent), lift 847. |
|
|
72
|
+
| aria-field-name | inputNoText | 82% co-rate, lift 2043 — likely fell out of "proposed" on a threshold technicality. Not ⚠️: evidence and semantics agree. |
|
|
73
|
+
| link-text-generic (+ -only) | linkVaguenessRisk | "Click here"-class generic text is exactly linkVaguenessRisk; runner-up linkTextsSame is a different defect (identical texts, different targets). Map both variants to the same issue; instance dedup absorbs the -only subset. This is pour's ACT sole-detector rule (aizyf1) — do not lose it. |
|
|
74
|
+
| form-label | labelConfusionRisk | Per the reading notes: the 66-element, lift-202 runner-up is the plausible mapping; the 2-element top pair was the quirk example. |
|
|
75
|
+
| media-captions | ⚠️ videoNoTranscript *or* new `mediaNoCaptions` (1.2.2) | Runner-up is close but captions ≠ transcript; Jeff's call whether tic's existing media family covers 1.2.2 or needs the new issue. |
|
|
76
|
+
|
|
77
|
+
## E. New-issue candidates — draft CONFIRM as genuinely new tic issues (11 rules → 8 issues)
|
|
78
|
+
|
|
79
|
+
The novelty pour was screened for. Suggested issue IDs follow tic naming style:
|
|
80
|
+
|
|
81
|
+
| New tic issue | Pour rule(s) | WCAG | Evidence |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| textSpacingRigid | text-spacing | 1.4.12 | No incumbent equivalent fired anywhere (580 el / 61 pages) |
|
|
84
|
+
| targetSmall | target-size | 2.5.8 AA | tic has no target-size issues (678 el) |
|
|
85
|
+
| targetSmallEnhanced | target-size-enhanced | 2.5.5 AAA | Mirror the contrastAA/AAA banding pattern (8,992 el — volume driver, watch quality) |
|
|
86
|
+
| nonTextContrast | non-text-contrast, control-contrast | 1.4.11 | No incumbent; control-contrast folded in from §C |
|
|
87
|
+
| focusIndicatorMissing | focus-visible | 2.4.7 | 167 pages — page-level rule |
|
|
88
|
+
| focusObscured | focus-not-obscured | 2.4.11 | New-in-2.2 criterion, no incumbent |
|
|
89
|
+
| motionNoControl | pause-stop-hide, video-loop-motion | 2.2.2 | Both halves of the auto-playing/looping motion defect |
|
|
90
|
+
| reflowBroken | reflow | 1.4.10 | From §C |
|
|
91
|
+
| pseudoHeading | p-as-heading | 1.3.1 | Visual heading not marked up; no incumbent fired |
|
|
92
|
+
| canvasNoText | canvas-alt | 1.1.1 | Canvas is not an img; imageNoText would blur element semantics |
|
|
93
|
+
| ariaRedundant | redundant-aria, redundant-aria-label, redundant-tabindex, redundant-role | best practice | Pour's "overdoing it" family as one issue, `quality: 1`; per-rule granularity preserved in instances |
|
|
94
|
+
|
|
95
|
+
Also: redundant-alt-phrase → new issue `altPhraseRedundant`, and note it likely also homes
|
|
96
|
+
the unmapped incumbents ed11y:altMeaningless/altMeaninglessLinked (kills two coverage-gap
|
|
97
|
+
birds). aria-valid-refs → new issue `ariaRefBroken` (broken ID references) unless Jeff reads
|
|
98
|
+
tic's ariaMissing as covering it.
|
|
99
|
+
|
|
100
|
+
## F. New-issue candidates — draft QUALITY-0 / park (2)
|
|
101
|
+
|
|
102
|
+
| Pour rule | Draft | Rationale |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| button-type | quality-0 | Implicit-submit is a behavior bug more than an a11y defect; weak evidence (17 el) |
|
|
105
|
+
| no-autofocus | quality-0 | Best-practice; genuine debate whether autofocus is a defect at all; 8 el |
|
|
106
|
+
|
|
107
|
+
## G. Low-support (11) — draft: park all except three semantic gifts
|
|
108
|
+
|
|
109
|
+
Park (insufficient evidence, revisit if Stage 4 surfaces them): definition-list,
|
|
110
|
+
aria-required-parent, label-for-valid, visual-order-divergence, auth-field-obstruction,
|
|
111
|
+
composite-widget-name, valid-role, fieldset-legend.
|
|
112
|
+
|
|
113
|
+
Map now despite low support (semantics are exact and unambiguous):
|
|
114
|
+
|
|
115
|
+
| Pour rule | tic issue | Note |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| area-alt | linkNoText | Image-map areas are links; 100% co-rate, 47 el (one page) |
|
|
118
|
+
| dialog-name | dialogNoText | 100% co-rate, semantic exact |
|
|
119
|
+
| table-headers | tableHeaderless | Runner-up is semantically exact |
|
|
120
|
+
|
|
121
|
+
## Tally
|
|
122
|
+
|
|
123
|
+
- 24 accepts + 3 splits + 8 remaps + 3 low-support maps = **38 mapped pour rules**
|
|
124
|
+
- 13 rules → **~13 new tic issues** (8 novel families incl. multi-rule groupings)
|
|
125
|
+
- 2 quality-0, 8 parked
|
|
126
|
+
- ⚠️ rows needing real human judgment: **9** (landmark-unique, the three §C rejects,
|
|
127
|
+
region/svg-img-alt/nested-interactive/aria-required-children remaps if the co-rate
|
|
128
|
+
bothers you, media-captions)
|
|
129
|
+
|
|
130
|
+
## After sign-off
|
|
131
|
+
|
|
132
|
+
1. Author tic entries (next tic version) in the testilo fork: mapped rules at `quality: 1`;
|
|
133
|
+
new issues with WCAG + weight; splits implemented in the mapping layer per §B.
|
|
134
|
+
2. Re-run the evaluation slice as the Stage-4 `study_runs` row with mappings live
|
|
135
|
+
(this is also the app-tier load-baseline measured window).
|
|
136
|
+
3. Byproduct filings: taxonomy coverage gaps (qualWeb QW-BP30 × 807, ed11y alt* family,
|
|
137
|
+
axe meta-refresh → homed by the §A/§E work above), pour FP classes with rates
|
|
138
|
+
(fp-triage-2026-08-22.md).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Track A — surea11y, full ACT run (2026-09-01)
|
|
2
|
+
|
|
3
|
+
Capture: `results/surea11y-full-2026-09-01.jsonl` (1,213 testcases, 1,196 ran, 17 prevented —
|
|
4
|
+
the known redirecting-fixture class). Scored `--band asserted`;
|
|
5
|
+
`results/surea11y-full-2026-09-01-asserted.json`. Comparison figures are the merged
|
|
6
|
+
2026-08-22 full run (`merged-full-2026-08-22-asserted.json`), same fixtures, same
|
|
7
|
+
criterion-level scoring layer for pour/axe/surea11y (qualWeb keeps its native-ACT scoring
|
|
8
|
+
advantage).
|
|
9
|
+
|
|
10
|
+
## Aggregate (any-engine detection subset — 47 rules with surea11y included, was 43)
|
|
11
|
+
|
|
12
|
+
| Engine | Sensitivity | Specificity | TP | FN | FP | TN |
|
|
13
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
14
|
+
| qualWeb | 85.9% | 99.1% | 213 | 35 | 5 | 527 |
|
|
15
|
+
| **surea11y** | **68.6%** | **92.0%** | 151 | 69 | 37 | 425 |
|
|
16
|
+
| pour | 51.1% | 91.7% | 112 | 107 | 37 | 407 |
|
|
17
|
+
| axe | 33.2% | 96.0% | 73 | 147 | 19 | 453 |
|
|
18
|
+
|
|
19
|
+
surea11y clears the Stage-2 gate: specificity ≥ incumbent median, and sensitivity is the
|
|
20
|
+
best of any criterion-layer engine — +17.5 points over pour at equal specificity.
|
|
21
|
+
|
|
22
|
+
## Net-new coverage
|
|
23
|
+
|
|
24
|
+
Sole detector across all four engines: `e88epe` (image not in accessibility tree is
|
|
25
|
+
decorative — 20% sens, but nobody else scores at all).
|
|
26
|
+
|
|
27
|
+
Detects where pour misses (7): the three **1.4.12 text-spacing** rules (24afc2 / 78fd32 /
|
|
28
|
+
9e45ec, all 100/100 — ACT-fixture confirmation of the same novelty pour's Stage-3b flagged
|
|
29
|
+
in the wild), **b33eff orientation lock** (100/100), **bisz58 meta-refresh no-exception**
|
|
30
|
+
(100/100), akn7bn iframe tab-order, e88epe.
|
|
31
|
+
|
|
32
|
+
Misses where pour detects (3): aizyf1 (link is descriptive — pour's marquee sole-source
|
|
33
|
+
stays unique to pour), 6cfa84 (aria-hidden focusable content), 80f0bf (auto-playing audio).
|
|
34
|
+
**The engines are complementary, not redundant** — each keeps sole-source value if both
|
|
35
|
+
were adopted.
|
|
36
|
+
|
|
37
|
+
## FP hotspots (Stage-3 should measure their in-the-wild rates)
|
|
38
|
+
|
|
39
|
+
| ACT rule | FP | Note |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| 4b1c6c iframe identical accessible names | 11 (TP 2) | Dominant FP class — over-asserts equivalence judgments the fixture set treats as `cantTell` territory |
|
|
42
|
+
| ucwvc8 page-language subtag matches content | 4 (TP 0) | Same lang-heuristic FP class pour had; pour's Stage-3a showed it ~absent in the wild |
|
|
43
|
+
| e086e5 / 2t702h / akn7bn / qt1vmo / bc4a75 / e88epe | 2–3 each | Small; triage with Stage-3 rates |
|
|
44
|
+
|
|
45
|
+
## Verdict
|
|
46
|
+
|
|
47
|
+
Stage-2 gate **passed**. Next per pipeline: Stage-3 capture over the evaluation slice
|
|
48
|
+
(`--urls yra-100k/data/stage3a-sample-300.txt`, engines surea11y + incumbents) — ran-rate,
|
|
49
|
+
latency distribution, trigger-happy screen, and the 4b1c6c FP class's real-world rate; then
|
|
50
|
+
3b mapping bootstrap off the same capture.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2022–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jonathan Robert Pool.
|
|
4
|
+
© 2026 Jeff Witt.
|
|
3
5
|
|
|
4
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -8,119 +10,207 @@
|
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
/*
|
|
11
|
-
|
|
12
|
-
Validator for network watching.
|
|
13
|
+
netWatch.js
|
|
14
|
+
Validator for network watching, covering all three auth types.
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
// IMPORTS
|
|
16
18
|
|
|
17
19
|
const fs = require('fs/promises');
|
|
20
|
+
const client = require('http');
|
|
18
21
|
|
|
19
22
|
// CONSTANTS
|
|
20
23
|
|
|
21
|
-
// Override netWatch environment variables with validation-specific ones.
|
|
22
24
|
const jobDir = `${__dirname}/../jobs/todo`;
|
|
23
|
-
process.env.JOB_URLS = 'http://localhost:3007/api/job';
|
|
24
|
-
process.env.AGENT = 'testarauth';
|
|
25
|
-
const {netWatch} = require('../../netWatch');
|
|
26
|
-
const client = require('http');
|
|
27
25
|
const jobID = '240101T1200-simple-example';
|
|
26
|
+
const workerID = 'testaro1';
|
|
27
|
+
const workerSecret = 'testarosecret';
|
|
28
|
+
// Override netWatch environment variables with validation-specific ones.
|
|
29
|
+
process.env.NETWATCH_URL_JOB = 'http://localhost:3007/api/job';
|
|
30
|
+
process.env.NETWATCH_URL_REPORT = 'http://localhost:3007/api/report';
|
|
31
|
+
process.env.NETWATCH_WORKER_ID = workerID;
|
|
32
|
+
process.env.NETWATCH_WORKER_SECRET = workerSecret;
|
|
33
|
+
const {netWatch} = require('../../netWatch');
|
|
34
|
+
/*
|
|
35
|
+
The authorization-header value netWatch is expected to send in header mode: per RFC 7617, Basic
|
|
36
|
+
followed by base64 of testaro1:testarosecret. Intentionally a literal, not derived from the
|
|
37
|
+
netWatch code, so an encoding regression there fails here.
|
|
38
|
+
*/
|
|
39
|
+
const workerAuth = 'Basic dGVzdGFybzE6dGVzdGFyb3NlY3JldA==';
|
|
40
|
+
// Auth types to be validated.
|
|
41
|
+
const authTypes = ['header', 'pathBody', 'none'];
|
|
28
42
|
|
|
29
|
-
//
|
|
43
|
+
// FUNCTIONS
|
|
30
44
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
// Returns an error description if a request carries wrong credentials for an auth type, else ''.
|
|
46
|
+
const credentialError = (authType, request, bodyObj) => {
|
|
47
|
+
const authHeader = request.headers.authorization;
|
|
48
|
+
if (authType === 'header') {
|
|
49
|
+
if (authHeader !== workerAuth) {
|
|
50
|
+
return 'authorization header missing or wrong';
|
|
51
|
+
}
|
|
52
|
+
if (bodyObj.agentPW !== undefined) {
|
|
53
|
+
return 'agentPW unexpectedly in the request body';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (authType === 'pathBody') {
|
|
57
|
+
if (authHeader) {
|
|
58
|
+
return 'authorization header unexpectedly present';
|
|
59
|
+
}
|
|
60
|
+
if (bodyObj.agentPW !== workerSecret) {
|
|
61
|
+
return 'agentPW missing from or wrong in the request body';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (authHeader) {
|
|
66
|
+
return 'authorization header unexpectedly present';
|
|
67
|
+
}
|
|
68
|
+
if (bodyObj.agentPW !== undefined) {
|
|
69
|
+
return 'agentPW unexpectedly in the request body';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return '';
|
|
73
|
+
};
|
|
74
|
+
// Validates netWatch with one auth type; returns whether the scenario succeeded.
|
|
75
|
+
const runScenario = authType => {
|
|
76
|
+
return new Promise(resolveScenario => {
|
|
77
|
+
process.env.NETWATCH_AUTH_TYPE = authType;
|
|
78
|
+
let jobGiven = false;
|
|
79
|
+
let noJobSent = false;
|
|
80
|
+
let reportOK = false;
|
|
81
|
+
let server;
|
|
82
|
+
// Responds to a request with a JSON object and a status code.
|
|
83
|
+
const respondWithObject = (object, response, statusCode = 200) => {
|
|
84
|
+
response.statusCode = statusCode;
|
|
85
|
+
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
86
|
+
response.end(JSON.stringify(object));
|
|
87
|
+
};
|
|
88
|
+
// Handles Testaro requests to the server.
|
|
89
|
+
const requestHandler = (request, response) => {
|
|
90
|
+
const {method} = request;
|
|
91
|
+
// Ignore any trailing slash on the request URL.
|
|
92
|
+
const url = request.url.replace(/\/$/, '');
|
|
93
|
+
const bodyParts = [];
|
|
94
|
+
request.on('error', err => {
|
|
95
|
+
console.error(err);
|
|
96
|
+
})
|
|
97
|
+
.on('data', chunk => {
|
|
98
|
+
bodyParts.push(chunk);
|
|
99
|
+
})
|
|
100
|
+
.on('end', async () => {
|
|
101
|
+
let bodyObj = {};
|
|
102
|
+
try {
|
|
103
|
+
bodyObj = JSON.parse(bodyParts.join('') || '{}');
|
|
65
104
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Send an empty-object response.
|
|
69
|
-
await response.end('{}');
|
|
105
|
+
catch(error) {
|
|
106
|
+
console.log(`Scenario ${authType} failure: non-JSON request body (${error.message})`);
|
|
70
107
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
// If the request carries wrong credentials for this auth type:
|
|
109
|
+
const credError = credentialError(authType, request, bodyObj);
|
|
110
|
+
if (credError) {
|
|
111
|
+
// Report this and reject the request; netWatch is expected to abort on this.
|
|
112
|
+
console.log(`Scenario ${authType} failure: ${credError}`);
|
|
113
|
+
respondWithObject({error: 'ERROR: Authorization invalid'}, response, 401);
|
|
114
|
+
server.close();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Otherwise, if the request is a job request:
|
|
118
|
+
if (method === 'POST' && url === '/api/job') {
|
|
119
|
+
console.log(`Server got a job request from Testaro (${authType})`);
|
|
120
|
+
// If this is the first job request of the scenario:
|
|
121
|
+
if (! noJobSent) {
|
|
122
|
+
// Send a no-job response, to validate the no-job branch.
|
|
123
|
+
noJobSent = true;
|
|
124
|
+
respondWithObject({}, response);
|
|
125
|
+
}
|
|
126
|
+
// Otherwise, i.e. if the no-job branch was already validated:
|
|
127
|
+
else {
|
|
128
|
+
// Respond with a job.
|
|
129
|
+
const jobJSON = await fs.readFile(`${jobDir}/${jobID}.json`);
|
|
130
|
+
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
131
|
+
response.end(jobJSON);
|
|
132
|
+
console.log(`Server sent job to Testaro (${authType})`);
|
|
133
|
+
jobGiven = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Otherwise, if the request is a report submission:
|
|
137
|
+
else if (method === 'POST' && url === '/api/report') {
|
|
138
|
+
console.log(`Server got report from Testaro (${authType})`);
|
|
139
|
+
const ack = {};
|
|
140
|
+
// If a job was earlier given to Testaro:
|
|
141
|
+
if (jobGiven) {
|
|
142
|
+
// Check the report, including the worker identification.
|
|
143
|
+
const {report} = bodyObj;
|
|
92
144
|
if (
|
|
93
|
-
|
|
94
|
-
&&
|
|
95
|
-
&&
|
|
96
|
-
&&
|
|
145
|
+
report
|
|
146
|
+
&& report.acts
|
|
147
|
+
&& report.jobData
|
|
148
|
+
&& report.sources
|
|
149
|
+
&& report.sources.agent === workerID
|
|
97
150
|
) {
|
|
98
151
|
ack.message = 'Success: Valid report submitted';
|
|
152
|
+
reportOK = true;
|
|
99
153
|
}
|
|
100
154
|
else {
|
|
101
|
-
ack.message = 'Failure: Report invalid';
|
|
155
|
+
ack.message = 'Failure: Report invalid or worker not identified';
|
|
102
156
|
}
|
|
103
157
|
}
|
|
104
|
-
|
|
105
|
-
ack.message =
|
|
158
|
+
else {
|
|
159
|
+
ack.message = 'ERROR: Report submitted before a job was given';
|
|
106
160
|
}
|
|
161
|
+
respondWithObject(ack, response);
|
|
162
|
+
console.log(`Server responded: ${ack.message}`);
|
|
163
|
+
// This ends the scenario, so stop the server.
|
|
164
|
+
server.close();
|
|
165
|
+
console.log('Server closed');
|
|
107
166
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
167
|
+
// Otherwise, i.e. if the request is neither:
|
|
168
|
+
else {
|
|
169
|
+
// Report this and reject the request.
|
|
170
|
+
console.log(`Scenario ${authType} failure: unexpected request ${method} ${request.url}`);
|
|
171
|
+
respondWithObject({error: 'ERROR: Request invalid'}, response, 404);
|
|
172
|
+
server.close();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
// Create a server.
|
|
177
|
+
server = client.createServer({}, requestHandler);
|
|
178
|
+
// Start the server listening for Testaro requests, then start a one-job watch.
|
|
179
|
+
server.listen(3007, async () => {
|
|
180
|
+
console.log(`Job and report server listening on port 3007 (auth type ${authType})`);
|
|
181
|
+
const watchOK = await netWatch(false, 5, false);
|
|
116
182
|
server.close();
|
|
117
|
-
|
|
118
|
-
}
|
|
183
|
+
resolveScenario(reportOK && watchOK);
|
|
184
|
+
});
|
|
119
185
|
});
|
|
120
186
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
187
|
+
|
|
188
|
+
// OPERATION
|
|
189
|
+
|
|
190
|
+
// Impose a deadline, so a hang becomes a failure instead of a silent stall.
|
|
191
|
+
const watchdog = setTimeout(() => {
|
|
192
|
+
console.log('Failure: netWatch validation timed out');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}, 600000);
|
|
195
|
+
// Run the scenarios in sequence and exit with a code reporting the outcome.
|
|
196
|
+
(async () => {
|
|
197
|
+
const failures = [];
|
|
198
|
+
for (const authType of authTypes) {
|
|
199
|
+
const isOK = await runScenario(authType);
|
|
200
|
+
// Drop any kept-alive connections to the closed server before the next scenario.
|
|
201
|
+
client.globalAgent.destroy();
|
|
202
|
+
console.log(`Scenario ${authType}: ${isOK ? 'valid' : 'INVALID'}\n`);
|
|
203
|
+
if (! isOK) {
|
|
204
|
+
failures.push(authType);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
clearTimeout(watchdog);
|
|
208
|
+
if (failures.length) {
|
|
209
|
+
console.log(`Failure: Invalid scenarios: ${failures.join(', ')}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.log('Success: Valid reports submitted for all auth types');
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jeff Witt.
|
|
3
4
|
|
|
4
5
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
6
|
https://opensource.org/license/mit/ for details.
|
|
@@ -13,6 +14,20 @@
|
|
|
13
14
|
Execution example: npm test focOp
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
const testID = process.argv[2];
|
|
17
17
|
const {validateTest} = require('../validateTest');
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
// Flushes the standard output and then exits, because lingering browser
|
|
20
|
+
// processes otherwise keep the event loop alive indefinitely.
|
|
21
|
+
const exitWith = code => {
|
|
22
|
+
process.stdout.write('', () => process.exit(code));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const testID = process.argv[2];
|
|
26
|
+
validateTest(testID)
|
|
27
|
+
.then(result => {
|
|
28
|
+
exitWith(result.success ? 0 : 1);
|
|
29
|
+
})
|
|
30
|
+
.catch(error => {
|
|
31
|
+
console.log(`ERROR validating test ${testID} (${error.message})`);
|
|
32
|
+
exitWith(1);
|
|
33
|
+
});
|