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.
Files changed (356) hide show
  1. package/.gitattributes +94 -0
  2. package/.github/workflows/ci.yml +58 -0
  3. package/.github/workflows/publish.yml +49 -0
  4. package/.github/workflows/typescript.yml +35 -0
  5. package/AGENTS.md +2 -2
  6. package/CLAUDE.md +24 -18
  7. package/CONTAINERS.md +9 -6
  8. package/CONTRIBUTING.md +29 -3
  9. package/Dockerfile +1 -1
  10. package/README.md +73 -19
  11. package/UPGRADES.md +4 -0
  12. package/actSpecs-doc.md +13 -1
  13. package/actSpecs.js +29 -5
  14. package/call.js +6 -5
  15. package/docker-compose.yml +8 -2
  16. package/docs/checkpoint-scanning.md +199 -0
  17. package/docs/standard-result-outcome.md +165 -0
  18. package/env.example +44 -10
  19. package/eslint.config.mjs +101 -0
  20. package/netWatch.js +372 -222
  21. package/package.json +46 -31
  22. package/pour/README.md +47 -0
  23. package/pour/pour.min.js +8 -0
  24. package/procs/actDo.js +808 -0
  25. package/procs/catalog.d.ts +17 -0
  26. package/procs/catalog.js +312 -221
  27. package/procs/catalog.ts +398 -0
  28. package/procs/checkpoint.js +110 -0
  29. package/procs/config.d.ts +35 -0
  30. package/procs/config.js +62 -0
  31. package/procs/dateTime.js +2 -1
  32. package/procs/doActs.js +442 -849
  33. package/procs/doTestAct.js +27 -121
  34. package/procs/flow.js +221 -0
  35. package/procs/generateRuleRegistry.js +94 -0
  36. package/procs/getSource.d.ts +26 -0
  37. package/procs/job.js +115 -1
  38. package/procs/launch.d.ts +40 -0
  39. package/procs/launch.js +239 -108
  40. package/procs/nu.d.ts +48 -0
  41. package/procs/scope.js +196 -0
  42. package/procs/shoot.d.ts +33 -0
  43. package/procs/standard.d.ts +18 -0
  44. package/procs/standard.js +83 -0
  45. package/procs/standard.ts +126 -0
  46. package/procs/testAct.js +145 -0
  47. package/procs/testaro.d.ts +20 -0
  48. package/procs/testaro.js +236 -189
  49. package/procs/testaro.ts +321 -0
  50. package/procs/userPath.js +126 -0
  51. package/procs/xPath.d.ts +4 -0
  52. package/procs/xPath.js +81 -69
  53. package/procs/xPath.ts +106 -0
  54. package/procs/xPathScript.d.ts +6 -0
  55. package/procs/xPathScript.js +70 -0
  56. package/run.js +3 -2
  57. package/surea11y/README.md +44 -0
  58. package/surea11y/surea11y.browser.js +14 -0
  59. package/testaro/adbID.d.ts +3 -0
  60. package/testaro/adbID.js +38 -41
  61. package/testaro/adbID.ts +66 -0
  62. package/testaro/allCapStyle.d.ts +3 -0
  63. package/testaro/allCapStyle.js +31 -34
  64. package/testaro/allCapStyle.ts +55 -0
  65. package/testaro/allCaps.d.ts +16 -0
  66. package/testaro/allCaps.js +179 -151
  67. package/testaro/allCaps.ts +228 -0
  68. package/testaro/allHidden.d.ts +13 -0
  69. package/testaro/allHidden.js +29 -30
  70. package/testaro/allHidden.ts +50 -0
  71. package/testaro/allSlanted.d.ts +3 -0
  72. package/testaro/allSlanted.js +30 -33
  73. package/testaro/allSlanted.ts +54 -0
  74. package/testaro/altScheme.d.ts +3 -0
  75. package/testaro/altScheme.js +27 -30
  76. package/testaro/altScheme.ts +50 -0
  77. package/testaro/attVal.d.ts +3 -0
  78. package/testaro/attVal.js +20 -21
  79. package/testaro/attVal.ts +52 -0
  80. package/testaro/autocomplete.d.ts +3 -0
  81. package/testaro/autocomplete.js +59 -75
  82. package/testaro/autocomplete.ts +101 -0
  83. package/testaro/bulk.d.ts +13 -0
  84. package/testaro/bulk.js +32 -33
  85. package/testaro/bulk.ts +55 -0
  86. package/testaro/buttonMenu.d.ts +9 -0
  87. package/testaro/buttonMenu.js +317 -319
  88. package/testaro/buttonMenu.ts +391 -0
  89. package/testaro/captionLoc.d.ts +3 -0
  90. package/testaro/captionLoc.js +17 -20
  91. package/testaro/captionLoc.ts +40 -0
  92. package/testaro/datalistRef.d.ts +3 -0
  93. package/testaro/datalistRef.js +33 -36
  94. package/testaro/datalistRef.ts +55 -0
  95. package/testaro/distortion.d.ts +3 -0
  96. package/testaro/distortion.js +57 -26
  97. package/testaro/distortion.ts +81 -0
  98. package/testaro/docType.d.ts +15 -0
  99. package/testaro/docType.js +25 -25
  100. package/testaro/docType.ts +45 -0
  101. package/testaro/dupAtt.d.ts +16 -0
  102. package/testaro/dupAtt.js +113 -104
  103. package/testaro/dupAtt.ts +144 -0
  104. package/testaro/elements.d.ts +6 -0
  105. package/testaro/elements.js +153 -153
  106. package/testaro/elements.ts +215 -0
  107. package/testaro/embAc.d.ts +3 -0
  108. package/testaro/embAc.js +19 -20
  109. package/testaro/embAc.ts +40 -0
  110. package/testaro/focAll.d.ts +13 -0
  111. package/testaro/focAll.js +185 -191
  112. package/testaro/focAll.ts +217 -0
  113. package/testaro/focAndOp.d.ts +3 -0
  114. package/testaro/focAndOp.js +100 -104
  115. package/testaro/focAndOp.ts +128 -0
  116. package/testaro/focInd.d.ts +3 -0
  117. package/testaro/focInd.js +64 -66
  118. package/testaro/focInd.ts +96 -0
  119. package/testaro/focVis.d.ts +3 -0
  120. package/testaro/focVis.js +28 -30
  121. package/testaro/focVis.ts +52 -0
  122. package/testaro/headEl.d.ts +10 -0
  123. package/testaro/headEl.js +61 -62
  124. package/testaro/headEl.ts +82 -0
  125. package/testaro/headingAmb.d.ts +3 -0
  126. package/testaro/headingAmb.js +54 -63
  127. package/testaro/headingAmb.ts +72 -0
  128. package/testaro/hovInd.d.ts +12 -0
  129. package/testaro/hovInd.js +163 -130
  130. package/testaro/hovInd.ts +199 -0
  131. package/testaro/hover.d.ts +3 -0
  132. package/testaro/hover.js +155 -126
  133. package/testaro/hover.ts +154 -0
  134. package/testaro/hr.d.ts +3 -0
  135. package/testaro/hr.js +14 -17
  136. package/testaro/hr.ts +36 -0
  137. package/testaro/imageLink.d.ts +3 -0
  138. package/testaro/imageLink.js +17 -20
  139. package/testaro/imageLink.ts +42 -0
  140. package/testaro/labClash.d.ts +3 -0
  141. package/testaro/labClash.js +31 -33
  142. package/testaro/labClash.ts +54 -0
  143. package/testaro/legendLoc.d.ts +3 -0
  144. package/testaro/legendLoc.js +17 -20
  145. package/testaro/legendLoc.ts +42 -0
  146. package/testaro/lineHeight.d.ts +3 -0
  147. package/testaro/lineHeight.js +43 -48
  148. package/testaro/lineHeight.ts +70 -0
  149. package/testaro/linkAmb.d.ts +7 -0
  150. package/testaro/linkAmb.js +79 -80
  151. package/testaro/linkAmb.ts +105 -0
  152. package/testaro/linkExt.d.ts +3 -0
  153. package/testaro/linkExt.js +13 -16
  154. package/testaro/linkExt.ts +35 -0
  155. package/testaro/linkOldAtt.d.ts +3 -0
  156. package/testaro/linkOldAtt.js +25 -28
  157. package/testaro/linkOldAtt.ts +48 -0
  158. package/testaro/linkTo.d.ts +3 -0
  159. package/testaro/linkTo.js +22 -22
  160. package/testaro/linkTo.ts +43 -0
  161. package/testaro/linkUl.d.ts +3 -0
  162. package/testaro/linkUl.js +30 -34
  163. package/testaro/linkUl.ts +54 -0
  164. package/testaro/miniText.d.ts +3 -0
  165. package/testaro/miniText.js +41 -44
  166. package/testaro/miniText.ts +68 -0
  167. package/testaro/motion.d.ts +10 -0
  168. package/testaro/motion.js +92 -96
  169. package/testaro/motion.ts +125 -0
  170. package/testaro/nonTable.d.ts +3 -0
  171. package/testaro/nonTable.js +39 -45
  172. package/testaro/nonTable.ts +66 -0
  173. package/testaro/optRoleSel.d.ts +3 -0
  174. package/testaro/optRoleSel.js +16 -19
  175. package/testaro/optRoleSel.ts +41 -0
  176. package/testaro/phOnly.d.ts +3 -0
  177. package/testaro/phOnly.js +18 -21
  178. package/testaro/phOnly.ts +43 -0
  179. package/testaro/pseudoP.d.ts +3 -0
  180. package/testaro/pseudoP.js +36 -38
  181. package/testaro/pseudoP.ts +59 -0
  182. package/testaro/radioSet.d.ts +3 -0
  183. package/testaro/radioSet.js +57 -59
  184. package/testaro/radioSet.ts +79 -0
  185. package/testaro/registry.d.ts +63 -0
  186. package/testaro/registry.js +67 -0
  187. package/testaro/registry.ts +141 -0
  188. package/testaro/role.d.ts +3 -0
  189. package/testaro/role.js +27 -29
  190. package/testaro/role.ts +53 -0
  191. package/testaro/secHeading.d.ts +3 -0
  192. package/testaro/secHeading.js +30 -33
  193. package/testaro/secHeading.ts +53 -0
  194. package/testaro/styleDiff.d.ts +25 -0
  195. package/testaro/styleDiff.js +248 -252
  196. package/testaro/styleDiff.ts +303 -0
  197. package/testaro/tabNav.d.ts +33 -0
  198. package/testaro/tabNav.js +272 -342
  199. package/testaro/tabNav.ts +454 -0
  200. package/testaro/targetsNear.d.ts +9 -0
  201. package/testaro/targetsNear.js +130 -132
  202. package/testaro/targetsNear.ts +160 -0
  203. package/testaro/textNodes.d.ts +6 -0
  204. package/testaro/textNodes.js +139 -135
  205. package/testaro/textNodes.ts +185 -0
  206. package/testaro/textSem.d.ts +3 -0
  207. package/testaro/textSem.js +25 -28
  208. package/testaro/textSem.ts +47 -0
  209. package/testaro/title.d.ts +9 -0
  210. package/testaro/title.js +16 -13
  211. package/testaro/title.ts +31 -0
  212. package/testaro/titledEl.d.ts +3 -0
  213. package/testaro/titledEl.js +15 -18
  214. package/testaro/titledEl.ts +38 -0
  215. package/testaro/zIndex.d.ts +3 -0
  216. package/testaro/zIndex.js +19 -22
  217. package/testaro/zIndex.ts +42 -0
  218. package/tests/alfa.d.ts +45 -0
  219. package/tests/alfa.js +138 -141
  220. package/tests/alfa.ts +214 -0
  221. package/tests/aslint.d.ts +33 -0
  222. package/tests/aslint.js +272 -249
  223. package/tests/aslint.ts +301 -0
  224. package/tests/axe.d.ts +27 -0
  225. package/tests/axe.js +199 -200
  226. package/tests/axe.ts +277 -0
  227. package/tests/ed11y.d.ts +28 -0
  228. package/tests/ed11y.js +141 -99
  229. package/tests/ed11y.ts +178 -0
  230. package/tests/htmlcs.d.ts +21 -0
  231. package/tests/htmlcs.js +174 -140
  232. package/tests/htmlcs.ts +181 -0
  233. package/tests/ibm.d.ts +52 -0
  234. package/tests/ibm.js +164 -166
  235. package/tests/ibm.ts +251 -0
  236. package/tests/nuVal.d.ts +13 -0
  237. package/tests/nuVal.js +107 -112
  238. package/tests/nuVal.ts +145 -0
  239. package/tests/nuVnu.d.ts +15 -0
  240. package/tests/nuVnu.js +141 -111
  241. package/tests/nuVnu.ts +144 -0
  242. package/tests/pour.d.ts +31 -0
  243. package/tests/pour.js +242 -0
  244. package/tests/pour.ts +273 -0
  245. package/tests/qualWeb.d.ts +39 -0
  246. package/tests/qualWeb.js +302 -272
  247. package/tests/qualWeb.ts +415 -0
  248. package/tests/surea11y.d.ts +33 -0
  249. package/tests/surea11y.js +288 -0
  250. package/tests/surea11y.ts +334 -0
  251. package/tests/testaro.d.ts +25 -0
  252. package/tests/testaro.js +745 -652
  253. package/tests/testaro.ts +862 -0
  254. package/tests/wave.d.ts +46 -0
  255. package/tests/wave.js +166 -177
  256. package/tests/wave.ts +252 -0
  257. package/tsconfig.json +17 -0
  258. package/types.d.ts +243 -0
  259. package/types.js +10 -0
  260. package/types.ts +376 -0
  261. package/validation/act/README.md +46 -0
  262. package/validation/act/capture.js +424 -0
  263. package/validation/act/chromium-issue-draft.md +66 -0
  264. package/validation/act/fp-triage-2026-08-22.md +86 -0
  265. package/validation/act/isolation-notes.md +159 -0
  266. package/validation/act/playwright-issue-draft.md +73 -0
  267. package/validation/act/propose-mappings.js +0 -0
  268. package/validation/act/repro-cdp-raw.js +105 -0
  269. package/validation/act/repro-metarefresh.js +65 -0
  270. package/validation/act/score.js +184 -0
  271. package/validation/act/stage3a-stress-report.md +69 -0
  272. package/validation/act/stage3b-mapping-proposals.md +94 -0
  273. package/validation/act/stage3b-triage-draft.md +138 -0
  274. package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
  275. package/validation/executors/netWatch.js +180 -90
  276. package/validation/executors/test.js +17 -2
  277. package/validation/executors/tests.js +118 -10
  278. package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
  279. package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
  280. package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
  281. package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
  282. package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
  283. package/validation/knownFailures.json +1 -0
  284. package/validation/tests/jobProperties/adbID.json +27 -2
  285. package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
  286. package/validation/tests/jobProperties/allCaps.json +25 -0
  287. package/validation/tests/jobProperties/allHidden.json +133 -13
  288. package/validation/tests/jobProperties/allSlanted.json +3 -3
  289. package/validation/tests/jobProperties/altScheme.json +23 -8
  290. package/validation/tests/jobProperties/attVal.json +57 -57
  291. package/validation/tests/jobProperties/autocomplete.json +11 -1
  292. package/validation/tests/jobProperties/bulk.json +6 -1
  293. package/validation/tests/jobProperties/buttonMenu.json +63 -42
  294. package/validation/tests/jobProperties/captionLoc.json +1 -6
  295. package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
  296. package/validation/tests/jobProperties/checkpoint-page.json +397 -0
  297. package/validation/tests/jobProperties/checkpoint.json +395 -0
  298. package/validation/tests/jobProperties/datalistRef.json +20 -5
  299. package/validation/tests/jobProperties/distortion.json +28 -3
  300. package/validation/tests/jobProperties/docType.json +2 -2
  301. package/validation/tests/jobProperties/dupAtt.json +48 -33
  302. package/validation/tests/jobProperties/elements.json +28 -28
  303. package/validation/tests/jobProperties/embAc.json +36 -31
  304. package/validation/tests/jobProperties/focAndOp.json +284 -0
  305. package/validation/tests/jobProperties/focInd.json +34 -34
  306. package/validation/tests/jobProperties/focVis.json +1 -1
  307. package/validation/tests/jobProperties/hover.json +39 -37
  308. package/validation/tests/jobProperties/hr.json +13 -3
  309. package/validation/tests/jobProperties/imageLink.json +0 -5
  310. package/validation/tests/jobProperties/labClash.json +68 -33
  311. package/validation/tests/jobProperties/legendLoc.json +1 -6
  312. package/validation/tests/jobProperties/lineHeight.json +2 -2
  313. package/validation/tests/jobProperties/linkAmb.json +13 -18
  314. package/validation/tests/jobProperties/linkExt.json +1 -1
  315. package/validation/tests/jobProperties/linkOldAtt.json +12 -2
  316. package/validation/tests/jobProperties/linkTo.json +1 -1
  317. package/validation/tests/jobProperties/linkUl.json +65 -65
  318. package/validation/tests/jobProperties/miniText.json +7 -2
  319. package/validation/tests/jobProperties/motion.json +4 -50
  320. package/validation/tests/jobProperties/nonTable.json +48 -3
  321. package/validation/tests/jobProperties/optRoleSel.json +12 -2
  322. package/validation/tests/jobProperties/phOnly.json +6 -16
  323. package/validation/tests/jobProperties/pseudoP.json +17 -2
  324. package/validation/tests/jobProperties/radioSet.json +30 -30
  325. package/validation/tests/jobProperties/role.json +25 -5
  326. package/validation/tests/jobProperties/secHeading.json +26 -21
  327. package/validation/tests/jobProperties/styleDiff.json +35 -5
  328. package/validation/tests/jobProperties/tabNav.json +3 -1
  329. package/validation/tests/jobProperties/targetsNear.json +237 -0
  330. package/validation/tests/jobProperties/textNodes.json +29 -29
  331. package/validation/tests/jobProperties/textSem.json +26 -1
  332. package/validation/tests/jobProperties/title.json +12 -2
  333. package/validation/tests/jobProperties/titledEl.json +39 -9
  334. package/validation/tests/jobProperties/userPath.json +317 -0
  335. package/validation/tests/jobProperties/zIndex.json +40 -35
  336. package/validation/tests/targets/allCapStyle/index.html +26 -0
  337. package/validation/tests/targets/checkpoint/index.html +38 -0
  338. package/validation/tests/targets/datalistRef/index.html +1 -1
  339. package/validation/tests/targets/focAndOp/bad.html +29 -0
  340. package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
  341. package/validation/tests/targets/focInd/bad.html +2 -1
  342. package/validation/tests/targets/headEl/index.html +10 -1
  343. package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
  344. package/validation/tests/targets/userPath/index.html +42 -0
  345. package/validation/validateTest.js +61 -9
  346. package/.claude/settings.local.json +0 -11
  347. package/.eslintrc.json +0 -41
  348. package/htmlcs/.eslintrc.json +0 -67
  349. package/validation/tests/jobProperties/linkTitle.json +0 -127
  350. package/validation/tests/jobProperties/opFoc.json +0 -164
  351. package/validation/tests/jobProperties/targetSmall.json +0 -152
  352. package/validation/tests/jobProperties/targetTiny.json +0 -142
  353. package/validation/tests/targets/focOp/bad.html +0 -25
  354. package/validation/tests/targets/linkTitle/index.html +0 -24
  355. package/validation/tests/targets/opFoc/bad.html +0 -26
  356. package/validation/tests/targets/opFoc/good.html +0 -23
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ /*
3
+ © 2026 Jeff Witt.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.reporter = void 0;
45
+ // IMPORTS
46
+ const fs = __importStar(require("fs/promises"));
47
+ const xPath_1 = require("../procs/xPath");
48
+ const standard_1 = require("../procs/standard");
49
+ /*
50
+ surea11y
51
+ Implements the SureA11y core engine (https://github.com/SureA11y/core) WCAG ruleset.
52
+ Compiled to surea11y.js by tsc (issue #73); edit this file, not the emitted one.
53
+
54
+ Upstream publishes a self-contained standalone browser bundle, vendored verbatim at
55
+ ../surea11y/surea11y.browser.js (MPL-2.0), exposing an `a11ycore` global with
56
+ `runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly)`. See
57
+ ../surea11y/README.md for the pinned version and verified result shape.
58
+
59
+ Instance policy (engine-candidacy-pipeline.md, Stage 1): `fail`-tier occurrences become
60
+ instances with outcome `failed` at ordinalSeverity 2-3; automatic-rule `cantTell`-tier
61
+ occurrences become instances with outcome `cantTell` at 0-1 (the engine grades per occurrence
62
+ via `occurrenceOutcome`, which takes precedence over the rule-level outcome), carrying the
63
+ engine's `uncertainty` code and `needed` guidance when it supplies them; `pass` and
64
+ `notApplicable` results and all `type: 'manual'` rules are tallied in `data` only and never
65
+ become instances.
66
+ */
67
+ // CONSTANTS
68
+ // Severity weights within a certainty band, mirroring tests/axe.ts.
69
+ const severityWeights = {
70
+ minor: 0,
71
+ moderate: 0,
72
+ serious: 1,
73
+ critical: 1
74
+ };
75
+ // FUNCTIONS
76
+ // Performs and reports the SureA11y tests.
77
+ const reporter = async (page, report, actIndex) => {
78
+ // Get the nonce, if any.
79
+ const act = report.acts[actIndex];
80
+ const { jobData } = report;
81
+ const scriptNonce = (jobData && jobData.lastScriptNonce);
82
+ // Initialize the act report.
83
+ let data = {};
84
+ const result = {
85
+ nativeResult: {},
86
+ standardResult: {}
87
+ };
88
+ const standard = report.standard !== 'no';
89
+ // If standard results are to be reported:
90
+ if (standard) {
91
+ // Initialize the standard result.
92
+ result.standardResult = (0, standard_1.getStandardResult)();
93
+ }
94
+ // Get the vendored tool bundle.
95
+ let script;
96
+ try {
97
+ script = await fs.readFile(`${__dirname}/../surea11y/surea11y.browser.js`, 'utf8');
98
+ }
99
+ catch (error) {
100
+ data.prevented = true;
101
+ data.error = 'ERROR: surea11y bundle missing (see surea11y/README.md for vendoring instructions)';
102
+ return { data, result };
103
+ }
104
+ // If the act is scoped to changed subtrees, the engine tests their nearest common ancestor
105
+ // (its context is one selector, resolved with querySelector).
106
+ const contextSelector = report.scope?.commonRoot || null;
107
+ // Perform the tests and populate the native result.
108
+ result.nativeResult = await page.evaluate(args => new Promise(async (resolve) => {
109
+ const { scriptNonce, script, contextSelector } = args;
110
+ // Detect page-CSP blocking of the injection (the violation event is
111
+ // dispatched as a task, so the check below waits a tick).
112
+ let cspBlocked = false;
113
+ document.addEventListener('securitypolicyviolation', event => {
114
+ if ((event.violatedDirective || '').startsWith('script-src')) {
115
+ cspBlocked = true;
116
+ }
117
+ });
118
+ // Add the tool script to the page.
119
+ const toolScript = document.createElement('script');
120
+ if (scriptNonce) {
121
+ toolScript.nonce = scriptNonce;
122
+ console.log(`Added nonce ${scriptNonce} to tool script`);
123
+ }
124
+ toolScript.textContent = script;
125
+ document.body.insertAdjacentElement('beforeend', toolScript);
126
+ // If the bundle failed to define its global:
127
+ if (typeof a11ycore === 'undefined' || typeof a11ycore.runa11yCoreInPage !== 'function') {
128
+ // Let a pending securitypolicyviolation event fire before diagnosing.
129
+ await new Promise(tick => setTimeout(tick, 0));
130
+ // A CSP block is a per-page condition, not a browser fault — report it
131
+ // distinctly (wedge canaries must not match this message).
132
+ resolve({
133
+ prevented: true,
134
+ error: cspBlocked
135
+ ? 'surea11y injection blocked by page CSP (script-src)'
136
+ : `a11ycore global not defined (contentType ${document.contentType}, `
137
+ + `scriptChars ${script.length}, readyState ${document.readyState})`
138
+ });
139
+ return;
140
+ }
141
+ try {
142
+ // Run the engine on the whole page with default options — every loaded
143
+ // rule at the default WCAG 2.2 target, matching the axe and pour
144
+ // adapters' kitchen-sink default; testilo's taxonomy decides per rule
145
+ // what counts downstream. runa11yCoreInPage is synchronous; await
146
+ // tolerates a future promise-returning version.
147
+ const sureReport = await a11ycore.runa11yCoreInPage(location.href, contextSelector, {}, null);
148
+ // Dotted WCAG criterion (e.g. 1.4.3) from a rule's normative mappings.
149
+ const criterionOf = (meta) => {
150
+ const mappings = meta && meta.normativeMappings || [];
151
+ for (const mapping of mappings) {
152
+ const requirement = String(mapping.requirement || '').replace(/^SC\s*/i, '');
153
+ if (/^\d+\.\d+\.\d+$/.test(requirement)) {
154
+ return requirement;
155
+ }
156
+ }
157
+ return '';
158
+ };
159
+ const violations = [];
160
+ const incomplete = [];
161
+ // Tallied-only buckets.
162
+ let passRuleCount = 0;
163
+ let inapplicableRuleCount = 0;
164
+ let manualRuleCount = 0;
165
+ let manualOccurrenceCount = 0;
166
+ // For each atomic rule that ran:
167
+ (sureReport.checksResults || []).forEach(check => {
168
+ const { ruleId, outcome, type, occurrences } = check;
169
+ // Manual rules are advisory by definition: tally only.
170
+ if (type === 'manual') {
171
+ manualRuleCount++;
172
+ manualOccurrenceCount += (occurrences || []).length;
173
+ return;
174
+ }
175
+ if (outcome === 'pass') {
176
+ passRuleCount++;
177
+ return;
178
+ }
179
+ // A notApplicable result may carry one scan-describing occurrence
180
+ // (empty selector) — never an element finding.
181
+ if (outcome === 'notApplicable') {
182
+ inapplicableRuleCount++;
183
+ return;
184
+ }
185
+ // fail or cantTell: convert occurrences to flat findings, honoring
186
+ // per-occurrence grading when the rule tiered its findings.
187
+ (occurrences || []).forEach(occurrence => {
188
+ const tier = occurrence.occurrenceOutcome || outcome;
189
+ const cssPath = occurrence.selector || '';
190
+ let xPath = '';
191
+ try {
192
+ const element = cssPath && document.querySelector(cssPath);
193
+ if (element && typeof window.getXPath === 'function') {
194
+ xPath = window.getXPath(element) ?? '';
195
+ }
196
+ }
197
+ catch (error) {
198
+ // Unresolvable path: instance falls back to excerpt only.
199
+ }
200
+ // Reason for uncertainty: per occurrence, else per check.
201
+ const uncertainty = occurrence.uncertainty || check.uncertainty || {};
202
+ const finding = {
203
+ ruleID: ruleId || '',
204
+ what: (occurrence.summary || check.title || '').replace(/\s+/g, ' ').trim(),
205
+ wcag: criterionOf(check.meta),
206
+ severity: (check.severity || 'moderate').toLowerCase(),
207
+ uncertainty: tier === 'cantTell' ? (uncertainty.code || '') : '',
208
+ needed: tier === 'cantTell' ? (uncertainty.needed || '') : '',
209
+ html: (occurrence.html || '').slice(0, 500),
210
+ cssPath,
211
+ xPath
212
+ };
213
+ (tier === 'fail' ? violations : incomplete).push(finding);
214
+ });
215
+ });
216
+ resolve({
217
+ engine: sureReport.engine || {},
218
+ violations,
219
+ incomplete,
220
+ passRuleCount,
221
+ inapplicableRuleCount,
222
+ manualRuleCount,
223
+ manualOccurrenceCount
224
+ });
225
+ }
226
+ catch (error) {
227
+ resolve({
228
+ prevented: true,
229
+ error: error.message
230
+ });
231
+ }
232
+ ;
233
+ }), {
234
+ scriptNonce,
235
+ script,
236
+ contextSelector
237
+ });
238
+ const { nativeResult } = result;
239
+ // If the tool ran and rules were selected:
240
+ if (!nativeResult.prevented && act.rules) {
241
+ // Remove results of other rules.
242
+ const { rules } = act;
243
+ nativeResult.violations = (nativeResult.violations || [])
244
+ .filter(finding => rules.includes(finding.ruleID));
245
+ nativeResult.incomplete = (nativeResult.incomplete || [])
246
+ .filter(finding => rules.includes(finding.ruleID));
247
+ }
248
+ // If the tool was prevented from running:
249
+ if (nativeResult.prevented) {
250
+ data.prevented = true;
251
+ data.error = nativeResult.error;
252
+ }
253
+ // Otherwise, if a standard result is to be reported:
254
+ else if (standard) {
255
+ const { standardResult } = result;
256
+ // Record the tallied-only bucket sizes.
257
+ data.passRuleCount = nativeResult.passRuleCount;
258
+ data.inapplicableRuleCount = nativeResult.inapplicableRuleCount;
259
+ data.manualRuleCount = nativeResult.manualRuleCount;
260
+ data.manualOccurrenceCount = nativeResult.manualOccurrenceCount;
261
+ // For each certainty band:
262
+ const bands = [
263
+ [nativeResult.incomplete || [], 0, 'cantTell'],
264
+ [nativeResult.violations || [], 2, 'failed']
265
+ ];
266
+ bands.forEach(([findings, baseSeverity, outcome]) => {
267
+ // For each native-result finding:
268
+ findings.forEach(finding => {
269
+ // Add a standard-result instance, forwarding only a known uncertainty code.
270
+ const { ruleID, what, severity, uncertainty, needed, xPath } = finding;
271
+ (0, standard_1.addInstance)(standardResult, {
272
+ ruleID,
273
+ what,
274
+ ordinalSeverity: baseSeverity + (severityWeights[severity] ?? 0),
275
+ outcome,
276
+ uncertainty: standard_1.UNCERTAINTY_CODES.includes(uncertainty) ? uncertainty : undefined,
277
+ needed,
278
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
279
+ });
280
+ });
281
+ });
282
+ }
283
+ return {
284
+ data,
285
+ result
286
+ };
287
+ };
288
+ exports.reporter = reporter;
@@ -0,0 +1,334 @@
1
+ /*
2
+ © 2026 Jeff Witt.
3
+
4
+ Licensed under the MIT License. See LICENSE file at the project root or
5
+ https://opensource.org/license/mit/ for details.
6
+
7
+ SPDX-License-Identifier: MIT
8
+ */
9
+
10
+ // IMPORTS
11
+
12
+ import * as fs from 'fs/promises';
13
+ import type {Page} from 'playwright';
14
+ import {getXPathCatalogIndex} from '../procs/xPath';
15
+ import {getStandardResult, addInstance, UNCERTAINTY_CODES} from '../procs/standard';
16
+ import type {Act, Outcome, Report, StandardResult} from '../types';
17
+
18
+ // TYPES
19
+
20
+ // The surea11y-act properties this reporter consumes.
21
+ interface SureAct extends Act {
22
+ rules?: string[];
23
+ }
24
+ // A surea11y severity (axe-style impact).
25
+ type SureSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
26
+ // One element finding, flattened from the engine's per-check results.
27
+ interface SureFinding {
28
+ ruleID: string;
29
+ what: string;
30
+ wcag: string;
31
+ severity: SureSeverity;
32
+ uncertainty: string;
33
+ needed: string;
34
+ html: string;
35
+ cssPath: string;
36
+ xPath: string;
37
+ }
38
+ // The native result: flattened findings and tallies, or a prevention report.
39
+ interface SureNativeResult {
40
+ engine?: Record<string, unknown>;
41
+ violations?: SureFinding[];
42
+ incomplete?: SureFinding[];
43
+ passRuleCount?: number;
44
+ inapplicableRuleCount?: number;
45
+ manualRuleCount?: number;
46
+ manualOccurrenceCount?: number;
47
+ prevented?: boolean;
48
+ error?: string;
49
+ }
50
+ // An uncertainty report of the engine (per occurrence or per check).
51
+ interface SureUncertainty {
52
+ code?: string;
53
+ needed?: string;
54
+ }
55
+ // One occurrence of a check result (verified against @surea11y/core 1.7.0).
56
+ interface SureOccurrence {
57
+ selector?: string;
58
+ html?: string;
59
+ summary?: string;
60
+ occurrenceOutcome?: 'fail' | 'cantTell';
61
+ uncertainty?: SureUncertainty;
62
+ }
63
+ // One check result.
64
+ interface SureCheck {
65
+ ruleId?: string;
66
+ outcome?: 'pass' | 'fail' | 'cantTell' | 'notApplicable';
67
+ severity?: string;
68
+ type?: 'automatic' | 'manual';
69
+ title?: string;
70
+ occurrences?: SureOccurrence[];
71
+ uncertainty?: SureUncertainty;
72
+ meta?: {normativeMappings?: {requirement?: string}[]};
73
+ }
74
+ // The engine report.
75
+ interface SureReport {
76
+ engine?: Record<string, unknown>;
77
+ checksResults?: SureCheck[];
78
+ }
79
+
80
+ /*
81
+ The a11ycore global that the vendored bundle defines in the page. Declared without a
82
+ value, so this declaration is erased at emit and the references inside the page.evaluate
83
+ callback remain bare page-global references.
84
+ */
85
+ declare const a11ycore: {
86
+ runa11yCoreInPage: (
87
+ pageUrl: string, contextSelector: string | null, engineOptions: Record<string, unknown>, runOnly: unknown
88
+ ) => SureReport | Promise<SureReport>;
89
+ };
90
+
91
+ /*
92
+ surea11y
93
+ Implements the SureA11y core engine (https://github.com/SureA11y/core) WCAG ruleset.
94
+ Compiled to surea11y.js by tsc (issue #73); edit this file, not the emitted one.
95
+
96
+ Upstream publishes a self-contained standalone browser bundle, vendored verbatim at
97
+ ../surea11y/surea11y.browser.js (MPL-2.0), exposing an `a11ycore` global with
98
+ `runa11yCoreInPage(pageUrl, contextSelector, engineOptions, runOnly)`. See
99
+ ../surea11y/README.md for the pinned version and verified result shape.
100
+
101
+ Instance policy (engine-candidacy-pipeline.md, Stage 1): `fail`-tier occurrences become
102
+ instances with outcome `failed` at ordinalSeverity 2-3; automatic-rule `cantTell`-tier
103
+ occurrences become instances with outcome `cantTell` at 0-1 (the engine grades per occurrence
104
+ via `occurrenceOutcome`, which takes precedence over the rule-level outcome), carrying the
105
+ engine's `uncertainty` code and `needed` guidance when it supplies them; `pass` and
106
+ `notApplicable` results and all `type: 'manual'` rules are tallied in `data` only and never
107
+ become instances.
108
+ */
109
+
110
+ // CONSTANTS
111
+
112
+ // Severity weights within a certainty band, mirroring tests/axe.ts.
113
+ const severityWeights: Record<SureSeverity, number> = {
114
+ minor: 0,
115
+ moderate: 0,
116
+ serious: 1,
117
+ critical: 1
118
+ };
119
+
120
+ // FUNCTIONS
121
+
122
+ // Performs and reports the SureA11y tests.
123
+ export const reporter = async (page: Page, report: Report, actIndex: number) => {
124
+ // Get the nonce, if any.
125
+ const act = report.acts[actIndex] as SureAct;
126
+ const {jobData} = report;
127
+ const scriptNonce = (jobData && jobData.lastScriptNonce) as string | undefined;
128
+ // Initialize the act report.
129
+ let data: Record<string, unknown> = {};
130
+ const result: {nativeResult: SureNativeResult; standardResult: StandardResult} = {
131
+ nativeResult: {},
132
+ standardResult: {}
133
+ };
134
+ const standard = report.standard !== 'no';
135
+ // If standard results are to be reported:
136
+ if (standard) {
137
+ // Initialize the standard result.
138
+ result.standardResult = getStandardResult();
139
+ }
140
+ // Get the vendored tool bundle.
141
+ let script: string;
142
+ try {
143
+ script = await fs.readFile(`${__dirname}/../surea11y/surea11y.browser.js`, 'utf8');
144
+ }
145
+ catch(error) {
146
+ data.prevented = true;
147
+ data.error = 'ERROR: surea11y bundle missing (see surea11y/README.md for vendoring instructions)';
148
+ return {data, result};
149
+ }
150
+ // If the act is scoped to changed subtrees, the engine tests their nearest common ancestor
151
+ // (its context is one selector, resolved with querySelector).
152
+ const contextSelector = report.scope?.commonRoot || null;
153
+ // Perform the tests and populate the native result.
154
+ result.nativeResult = await page.evaluate(args => new Promise<SureNativeResult>(
155
+ async resolve => {
156
+ const {scriptNonce, script, contextSelector} = args;
157
+ // Detect page-CSP blocking of the injection (the violation event is
158
+ // dispatched as a task, so the check below waits a tick).
159
+ let cspBlocked = false;
160
+ document.addEventListener('securitypolicyviolation', event => {
161
+ if ((event.violatedDirective || '').startsWith('script-src')) {
162
+ cspBlocked = true;
163
+ }
164
+ });
165
+ // Add the tool script to the page.
166
+ const toolScript = document.createElement('script');
167
+ if (scriptNonce) {
168
+ toolScript.nonce = scriptNonce;
169
+ console.log(`Added nonce ${scriptNonce} to tool script`);
170
+ }
171
+ toolScript.textContent = script;
172
+ document.body.insertAdjacentElement('beforeend', toolScript);
173
+ // If the bundle failed to define its global:
174
+ if (typeof a11ycore === 'undefined' || typeof a11ycore.runa11yCoreInPage !== 'function') {
175
+ // Let a pending securitypolicyviolation event fire before diagnosing.
176
+ await new Promise(tick => setTimeout(tick, 0));
177
+ // A CSP block is a per-page condition, not a browser fault — report it
178
+ // distinctly (wedge canaries must not match this message).
179
+ resolve({
180
+ prevented: true,
181
+ error: cspBlocked
182
+ ? 'surea11y injection blocked by page CSP (script-src)'
183
+ : `a11ycore global not defined (contentType ${document.contentType}, `
184
+ + `scriptChars ${script.length}, readyState ${document.readyState})`
185
+ });
186
+ return;
187
+ }
188
+ try {
189
+ // Run the engine on the whole page with default options — every loaded
190
+ // rule at the default WCAG 2.2 target, matching the axe and pour
191
+ // adapters' kitchen-sink default; testilo's taxonomy decides per rule
192
+ // what counts downstream. runa11yCoreInPage is synchronous; await
193
+ // tolerates a future promise-returning version.
194
+ const sureReport = await a11ycore.runa11yCoreInPage(location.href, contextSelector, {}, null);
195
+ // Dotted WCAG criterion (e.g. 1.4.3) from a rule's normative mappings.
196
+ const criterionOf = (meta: SureCheck['meta']): string => {
197
+ const mappings = meta && meta.normativeMappings || [];
198
+ for (const mapping of mappings) {
199
+ const requirement = String(mapping.requirement || '').replace(/^SC\s*/i, '');
200
+ if (/^\d+\.\d+\.\d+$/.test(requirement)) {
201
+ return requirement;
202
+ }
203
+ }
204
+ return '';
205
+ };
206
+ const violations: SureFinding[] = [];
207
+ const incomplete: SureFinding[] = [];
208
+ // Tallied-only buckets.
209
+ let passRuleCount = 0;
210
+ let inapplicableRuleCount = 0;
211
+ let manualRuleCount = 0;
212
+ let manualOccurrenceCount = 0;
213
+ // For each atomic rule that ran:
214
+ (sureReport.checksResults || []).forEach(check => {
215
+ const {ruleId, outcome, type, occurrences} = check;
216
+ // Manual rules are advisory by definition: tally only.
217
+ if (type === 'manual') {
218
+ manualRuleCount++;
219
+ manualOccurrenceCount += (occurrences || []).length;
220
+ return;
221
+ }
222
+ if (outcome === 'pass') {
223
+ passRuleCount++;
224
+ return;
225
+ }
226
+ // A notApplicable result may carry one scan-describing occurrence
227
+ // (empty selector) — never an element finding.
228
+ if (outcome === 'notApplicable') {
229
+ inapplicableRuleCount++;
230
+ return;
231
+ }
232
+ // fail or cantTell: convert occurrences to flat findings, honoring
233
+ // per-occurrence grading when the rule tiered its findings.
234
+ (occurrences || []).forEach(occurrence => {
235
+ const tier = occurrence.occurrenceOutcome || outcome;
236
+ const cssPath = occurrence.selector || '';
237
+ let xPath = '';
238
+ try {
239
+ const element = cssPath && document.querySelector(cssPath);
240
+ if (element && typeof window.getXPath === 'function') {
241
+ xPath = window.getXPath(element) ?? '';
242
+ }
243
+ }
244
+ catch(error) {
245
+ // Unresolvable path: instance falls back to excerpt only.
246
+ }
247
+ // Reason for uncertainty: per occurrence, else per check.
248
+ const uncertainty = occurrence.uncertainty || check.uncertainty || {};
249
+ const finding: SureFinding = {
250
+ ruleID: ruleId || '',
251
+ what: (occurrence.summary || check.title || '').replace(/\s+/g, ' ').trim(),
252
+ wcag: criterionOf(check.meta),
253
+ severity: (check.severity || 'moderate').toLowerCase() as SureSeverity,
254
+ uncertainty: tier === 'cantTell' ? (uncertainty.code || '') : '',
255
+ needed: tier === 'cantTell' ? (uncertainty.needed || '') : '',
256
+ html: (occurrence.html || '').slice(0, 500),
257
+ cssPath,
258
+ xPath
259
+ };
260
+ (tier === 'fail' ? violations : incomplete).push(finding);
261
+ });
262
+ });
263
+ resolve({
264
+ engine: sureReport.engine || {},
265
+ violations,
266
+ incomplete,
267
+ passRuleCount,
268
+ inapplicableRuleCount,
269
+ manualRuleCount,
270
+ manualOccurrenceCount
271
+ });
272
+ }
273
+ catch(error) {
274
+ resolve({
275
+ prevented: true,
276
+ error: (error as Error).message
277
+ });
278
+ };
279
+ }
280
+ ), {
281
+ scriptNonce,
282
+ script,
283
+ contextSelector
284
+ });
285
+ const {nativeResult} = result;
286
+ // If the tool ran and rules were selected:
287
+ if (! nativeResult.prevented && act.rules) {
288
+ // Remove results of other rules.
289
+ const {rules} = act;
290
+ nativeResult.violations = (nativeResult.violations || [])
291
+ .filter(finding => rules.includes(finding.ruleID));
292
+ nativeResult.incomplete = (nativeResult.incomplete || [])
293
+ .filter(finding => rules.includes(finding.ruleID));
294
+ }
295
+ // If the tool was prevented from running:
296
+ if (nativeResult.prevented) {
297
+ data.prevented = true;
298
+ data.error = nativeResult.error;
299
+ }
300
+ // Otherwise, if a standard result is to be reported:
301
+ else if (standard) {
302
+ const {standardResult} = result;
303
+ // Record the tallied-only bucket sizes.
304
+ data.passRuleCount = nativeResult.passRuleCount;
305
+ data.inapplicableRuleCount = nativeResult.inapplicableRuleCount;
306
+ data.manualRuleCount = nativeResult.manualRuleCount;
307
+ data.manualOccurrenceCount = nativeResult.manualOccurrenceCount;
308
+ // For each certainty band:
309
+ const bands: [SureFinding[], number, Outcome][] = [
310
+ [nativeResult.incomplete || [], 0, 'cantTell'],
311
+ [nativeResult.violations || [], 2, 'failed']
312
+ ];
313
+ bands.forEach(([findings, baseSeverity, outcome]) => {
314
+ // For each native-result finding:
315
+ findings.forEach(finding => {
316
+ // Add a standard-result instance, forwarding only a known uncertainty code.
317
+ const {ruleID, what, severity, uncertainty, needed, xPath} = finding;
318
+ addInstance(standardResult, {
319
+ ruleID,
320
+ what,
321
+ ordinalSeverity: baseSeverity + (severityWeights[severity] ?? 0),
322
+ outcome,
323
+ uncertainty: (UNCERTAINTY_CODES as string[]).includes(uncertainty) ? uncertainty : undefined,
324
+ needed,
325
+ catalogIndex: getXPathCatalogIndex(report, xPath)
326
+ });
327
+ });
328
+ });
329
+ }
330
+ return {
331
+ data,
332
+ result
333
+ };
334
+ };
@@ -0,0 +1,25 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Outcome, Report, StandardInstance } from '../types';
3
+ export declare const reporter: (page: Page | undefined, report: Report, actIndex: number) => Promise<{
4
+ data: {
5
+ prevented: boolean;
6
+ error: string;
7
+ rulePreventions: Record<string, string>;
8
+ rulesInvalid: string[];
9
+ ruleTestTimes: [string, number][];
10
+ ruleData: Record<string, unknown>;
11
+ scope?: {
12
+ localRules: string[];
13
+ pageRules: string[];
14
+ };
15
+ };
16
+ result: {
17
+ nativeResult: Record<string, unknown>;
18
+ standardResult: {
19
+ prevented: boolean;
20
+ totals: number[];
21
+ outcomeTotals: Record<Outcome, number>;
22
+ instances: StandardInstance[];
23
+ };
24
+ };
25
+ }>;