testaro 78.0.6 → 78.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) 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 +25 -19
  7. package/CONTAINERS.md +10 -6
  8. package/CONTRIBUTING.md +29 -3
  9. package/Dockerfile +1 -1
  10. package/README.md +75 -20
  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 +373 -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 +313 -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 +63 -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/getSource.js +1 -0
  38. package/procs/job.js +116 -1
  39. package/procs/launch.d.ts +40 -0
  40. package/procs/launch.js +239 -108
  41. package/procs/nu.d.ts +48 -0
  42. package/procs/nu.js +1 -0
  43. package/procs/scope.js +196 -0
  44. package/procs/shoot.d.ts +33 -0
  45. package/procs/shoot.js +3 -1
  46. package/procs/standard.d.ts +18 -0
  47. package/procs/standard.js +83 -0
  48. package/procs/standard.ts +126 -0
  49. package/procs/testAct.js +145 -0
  50. package/procs/testaro.d.ts +20 -0
  51. package/procs/testaro.js +237 -189
  52. package/procs/testaro.ts +321 -0
  53. package/procs/userPath.js +126 -0
  54. package/procs/xPath.d.ts +4 -0
  55. package/procs/xPath.js +82 -64
  56. package/procs/xPath.ts +106 -0
  57. package/procs/xPathScript.d.ts +6 -0
  58. package/procs/xPathScript.js +70 -0
  59. package/run.js +3 -2
  60. package/surea11y/README.md +44 -0
  61. package/surea11y/surea11y.browser.js +14 -0
  62. package/testaro/adbID.d.ts +3 -0
  63. package/testaro/adbID.js +39 -41
  64. package/testaro/adbID.ts +66 -0
  65. package/testaro/allCapStyle.d.ts +3 -0
  66. package/testaro/allCapStyle.js +32 -34
  67. package/testaro/allCapStyle.ts +55 -0
  68. package/testaro/allCaps.d.ts +16 -0
  69. package/testaro/allCaps.js +179 -151
  70. package/testaro/allCaps.ts +228 -0
  71. package/testaro/allHidden.d.ts +13 -0
  72. package/testaro/allHidden.js +30 -30
  73. package/testaro/allHidden.ts +50 -0
  74. package/testaro/allSlanted.d.ts +3 -0
  75. package/testaro/allSlanted.js +31 -33
  76. package/testaro/allSlanted.ts +54 -0
  77. package/testaro/altScheme.d.ts +3 -0
  78. package/testaro/altScheme.js +28 -30
  79. package/testaro/altScheme.ts +50 -0
  80. package/testaro/attVal.d.ts +3 -0
  81. package/testaro/attVal.js +21 -21
  82. package/testaro/attVal.ts +52 -0
  83. package/testaro/autocomplete.d.ts +3 -0
  84. package/testaro/autocomplete.js +60 -75
  85. package/testaro/autocomplete.ts +101 -0
  86. package/testaro/bulk.d.ts +13 -0
  87. package/testaro/bulk.js +33 -33
  88. package/testaro/bulk.ts +55 -0
  89. package/testaro/buttonMenu.d.ts +9 -0
  90. package/testaro/buttonMenu.js +318 -319
  91. package/testaro/buttonMenu.ts +391 -0
  92. package/testaro/captionLoc.d.ts +3 -0
  93. package/testaro/captionLoc.js +18 -20
  94. package/testaro/captionLoc.ts +40 -0
  95. package/testaro/datalistRef.d.ts +3 -0
  96. package/testaro/datalistRef.js +34 -36
  97. package/testaro/datalistRef.ts +55 -0
  98. package/testaro/distortion.d.ts +3 -0
  99. package/testaro/distortion.js +58 -26
  100. package/testaro/distortion.ts +81 -0
  101. package/testaro/docType.d.ts +15 -0
  102. package/testaro/docType.js +26 -25
  103. package/testaro/docType.ts +45 -0
  104. package/testaro/dupAtt.d.ts +16 -0
  105. package/testaro/dupAtt.js +114 -104
  106. package/testaro/dupAtt.ts +144 -0
  107. package/testaro/elements.d.ts +6 -0
  108. package/testaro/elements.js +153 -153
  109. package/testaro/elements.ts +215 -0
  110. package/testaro/embAc.d.ts +3 -0
  111. package/testaro/embAc.js +20 -20
  112. package/testaro/embAc.ts +40 -0
  113. package/testaro/focAll.d.ts +13 -0
  114. package/testaro/focAll.js +186 -191
  115. package/testaro/focAll.ts +217 -0
  116. package/testaro/focAndOp.d.ts +3 -0
  117. package/testaro/focAndOp.js +101 -104
  118. package/testaro/focAndOp.ts +128 -0
  119. package/testaro/focInd.d.ts +3 -0
  120. package/testaro/focInd.js +65 -66
  121. package/testaro/focInd.ts +96 -0
  122. package/testaro/focVis.d.ts +3 -0
  123. package/testaro/focVis.js +29 -30
  124. package/testaro/focVis.ts +52 -0
  125. package/testaro/headEl.d.ts +10 -0
  126. package/testaro/headEl.js +62 -62
  127. package/testaro/headEl.ts +82 -0
  128. package/testaro/headingAmb.d.ts +3 -0
  129. package/testaro/headingAmb.js +55 -63
  130. package/testaro/headingAmb.ts +72 -0
  131. package/testaro/hovInd.d.ts +12 -0
  132. package/testaro/hovInd.js +164 -130
  133. package/testaro/hovInd.ts +199 -0
  134. package/testaro/hover.d.ts +3 -0
  135. package/testaro/hover.js +156 -126
  136. package/testaro/hover.ts +154 -0
  137. package/testaro/hr.d.ts +3 -0
  138. package/testaro/hr.js +15 -17
  139. package/testaro/hr.ts +36 -0
  140. package/testaro/imageLink.d.ts +3 -0
  141. package/testaro/imageLink.js +18 -20
  142. package/testaro/imageLink.ts +42 -0
  143. package/testaro/labClash.d.ts +3 -0
  144. package/testaro/labClash.js +32 -33
  145. package/testaro/labClash.ts +54 -0
  146. package/testaro/legendLoc.d.ts +3 -0
  147. package/testaro/legendLoc.js +18 -20
  148. package/testaro/legendLoc.ts +42 -0
  149. package/testaro/lineHeight.d.ts +3 -0
  150. package/testaro/lineHeight.js +44 -48
  151. package/testaro/lineHeight.ts +70 -0
  152. package/testaro/linkAmb.d.ts +7 -0
  153. package/testaro/linkAmb.js +80 -80
  154. package/testaro/linkAmb.ts +105 -0
  155. package/testaro/linkExt.d.ts +3 -0
  156. package/testaro/linkExt.js +14 -16
  157. package/testaro/linkExt.ts +35 -0
  158. package/testaro/linkOldAtt.d.ts +3 -0
  159. package/testaro/linkOldAtt.js +26 -28
  160. package/testaro/linkOldAtt.ts +48 -0
  161. package/testaro/linkTo.d.ts +3 -0
  162. package/testaro/linkTo.js +23 -22
  163. package/testaro/linkTo.ts +43 -0
  164. package/testaro/linkUl.d.ts +3 -0
  165. package/testaro/linkUl.js +31 -34
  166. package/testaro/linkUl.ts +54 -0
  167. package/testaro/miniText.d.ts +3 -0
  168. package/testaro/miniText.js +42 -44
  169. package/testaro/miniText.ts +68 -0
  170. package/testaro/motion.d.ts +10 -0
  171. package/testaro/motion.js +93 -96
  172. package/testaro/motion.ts +125 -0
  173. package/testaro/nonTable.d.ts +3 -0
  174. package/testaro/nonTable.js +40 -45
  175. package/testaro/nonTable.ts +66 -0
  176. package/testaro/optRoleSel.d.ts +3 -0
  177. package/testaro/optRoleSel.js +17 -19
  178. package/testaro/optRoleSel.ts +41 -0
  179. package/testaro/phOnly.d.ts +3 -0
  180. package/testaro/phOnly.js +19 -21
  181. package/testaro/phOnly.ts +43 -0
  182. package/testaro/pseudoP.d.ts +3 -0
  183. package/testaro/pseudoP.js +37 -38
  184. package/testaro/pseudoP.ts +59 -0
  185. package/testaro/radioSet.d.ts +3 -0
  186. package/testaro/radioSet.js +58 -59
  187. package/testaro/radioSet.ts +79 -0
  188. package/testaro/registry.d.ts +63 -0
  189. package/testaro/registry.js +67 -0
  190. package/testaro/registry.ts +141 -0
  191. package/testaro/role.d.ts +3 -0
  192. package/testaro/role.js +28 -29
  193. package/testaro/role.ts +53 -0
  194. package/testaro/secHeading.d.ts +3 -0
  195. package/testaro/secHeading.js +31 -33
  196. package/testaro/secHeading.ts +53 -0
  197. package/testaro/styleDiff.d.ts +25 -0
  198. package/testaro/styleDiff.js +249 -252
  199. package/testaro/styleDiff.ts +303 -0
  200. package/testaro/tabNav.d.ts +33 -0
  201. package/testaro/tabNav.js +273 -342
  202. package/testaro/tabNav.ts +454 -0
  203. package/testaro/targetsNear.d.ts +9 -0
  204. package/testaro/targetsNear.js +131 -132
  205. package/testaro/targetsNear.ts +160 -0
  206. package/testaro/textNodes.d.ts +6 -0
  207. package/testaro/textNodes.js +139 -135
  208. package/testaro/textNodes.ts +185 -0
  209. package/testaro/textSem.d.ts +3 -0
  210. package/testaro/textSem.js +26 -28
  211. package/testaro/textSem.ts +47 -0
  212. package/testaro/title.d.ts +9 -0
  213. package/testaro/title.js +16 -13
  214. package/testaro/title.ts +31 -0
  215. package/testaro/titledEl.d.ts +3 -0
  216. package/testaro/titledEl.js +16 -18
  217. package/testaro/titledEl.ts +38 -0
  218. package/testaro/zIndex.d.ts +3 -0
  219. package/testaro/zIndex.js +20 -22
  220. package/testaro/zIndex.ts +42 -0
  221. package/tests/alfa.d.ts +45 -0
  222. package/tests/alfa.js +139 -141
  223. package/tests/alfa.ts +214 -0
  224. package/tests/aslint.d.ts +33 -0
  225. package/tests/aslint.js +273 -249
  226. package/tests/aslint.ts +301 -0
  227. package/tests/axe.d.ts +27 -0
  228. package/tests/axe.js +200 -200
  229. package/tests/axe.ts +277 -0
  230. package/tests/ed11y.d.ts +28 -0
  231. package/tests/ed11y.js +142 -99
  232. package/tests/ed11y.ts +178 -0
  233. package/tests/htmlcs.d.ts +21 -0
  234. package/tests/htmlcs.js +175 -140
  235. package/tests/htmlcs.ts +181 -0
  236. package/tests/ibm.d.ts +52 -0
  237. package/tests/ibm.js +165 -166
  238. package/tests/ibm.ts +251 -0
  239. package/tests/nuVal.d.ts +13 -0
  240. package/tests/nuVal.js +107 -112
  241. package/tests/nuVal.ts +145 -0
  242. package/tests/nuVnu.d.ts +15 -0
  243. package/tests/nuVnu.js +142 -111
  244. package/tests/nuVnu.ts +144 -0
  245. package/tests/pour.d.ts +31 -0
  246. package/tests/pour.js +242 -0
  247. package/tests/pour.ts +273 -0
  248. package/tests/qualWeb.d.ts +39 -0
  249. package/tests/qualWeb.js +303 -272
  250. package/tests/qualWeb.ts +415 -0
  251. package/tests/surea11y.d.ts +33 -0
  252. package/tests/surea11y.js +288 -0
  253. package/tests/surea11y.ts +334 -0
  254. package/tests/testaro.d.ts +25 -0
  255. package/tests/testaro.js +746 -652
  256. package/tests/testaro.ts +862 -0
  257. package/tests/wave.d.ts +46 -0
  258. package/tests/wave.js +167 -177
  259. package/tests/wave.ts +252 -0
  260. package/tsconfig.json +17 -0
  261. package/types.d.ts +243 -0
  262. package/types.js +10 -0
  263. package/types.ts +376 -0
  264. package/validation/act/README.md +46 -0
  265. package/validation/act/capture.js +424 -0
  266. package/validation/act/chromium-issue-draft.md +66 -0
  267. package/validation/act/fp-triage-2026-08-22.md +86 -0
  268. package/validation/act/isolation-notes.md +159 -0
  269. package/validation/act/playwright-issue-draft.md +73 -0
  270. package/validation/act/propose-mappings.js +0 -0
  271. package/validation/act/repro-cdp-raw.js +105 -0
  272. package/validation/act/repro-metarefresh.js +65 -0
  273. package/validation/act/score.js +184 -0
  274. package/validation/act/stage3a-stress-report.md +69 -0
  275. package/validation/act/stage3b-mapping-proposals.md +94 -0
  276. package/validation/act/stage3b-triage-draft.md +138 -0
  277. package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
  278. package/validation/executors/netWatch.js +180 -90
  279. package/validation/executors/test.js +17 -2
  280. package/validation/executors/tests.js +118 -10
  281. package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
  282. package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
  283. package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
  284. package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
  285. package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
  286. package/validation/knownFailures.json +1 -0
  287. package/validation/tests/jobProperties/adbID.json +27 -2
  288. package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
  289. package/validation/tests/jobProperties/allCaps.json +25 -0
  290. package/validation/tests/jobProperties/allHidden.json +133 -13
  291. package/validation/tests/jobProperties/allSlanted.json +3 -3
  292. package/validation/tests/jobProperties/altScheme.json +23 -8
  293. package/validation/tests/jobProperties/attVal.json +57 -57
  294. package/validation/tests/jobProperties/autocomplete.json +11 -1
  295. package/validation/tests/jobProperties/bulk.json +6 -1
  296. package/validation/tests/jobProperties/buttonMenu.json +63 -42
  297. package/validation/tests/jobProperties/captionLoc.json +1 -6
  298. package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
  299. package/validation/tests/jobProperties/checkpoint-page.json +397 -0
  300. package/validation/tests/jobProperties/checkpoint.json +395 -0
  301. package/validation/tests/jobProperties/datalistRef.json +20 -5
  302. package/validation/tests/jobProperties/distortion.json +28 -3
  303. package/validation/tests/jobProperties/docType.json +2 -2
  304. package/validation/tests/jobProperties/dupAtt.json +48 -33
  305. package/validation/tests/jobProperties/elements.json +28 -28
  306. package/validation/tests/jobProperties/embAc.json +36 -31
  307. package/validation/tests/jobProperties/focAndOp.json +284 -0
  308. package/validation/tests/jobProperties/focInd.json +34 -34
  309. package/validation/tests/jobProperties/focVis.json +1 -1
  310. package/validation/tests/jobProperties/hover.json +39 -37
  311. package/validation/tests/jobProperties/hr.json +13 -3
  312. package/validation/tests/jobProperties/imageLink.json +0 -5
  313. package/validation/tests/jobProperties/labClash.json +68 -33
  314. package/validation/tests/jobProperties/legendLoc.json +1 -6
  315. package/validation/tests/jobProperties/lineHeight.json +2 -2
  316. package/validation/tests/jobProperties/linkAmb.json +13 -18
  317. package/validation/tests/jobProperties/linkExt.json +1 -1
  318. package/validation/tests/jobProperties/linkOldAtt.json +12 -2
  319. package/validation/tests/jobProperties/linkTo.json +1 -1
  320. package/validation/tests/jobProperties/linkUl.json +65 -65
  321. package/validation/tests/jobProperties/miniText.json +7 -2
  322. package/validation/tests/jobProperties/motion.json +4 -50
  323. package/validation/tests/jobProperties/nonTable.json +48 -3
  324. package/validation/tests/jobProperties/optRoleSel.json +12 -2
  325. package/validation/tests/jobProperties/phOnly.json +6 -16
  326. package/validation/tests/jobProperties/pseudoP.json +17 -2
  327. package/validation/tests/jobProperties/radioSet.json +30 -30
  328. package/validation/tests/jobProperties/role.json +25 -5
  329. package/validation/tests/jobProperties/secHeading.json +26 -21
  330. package/validation/tests/jobProperties/styleDiff.json +35 -5
  331. package/validation/tests/jobProperties/tabNav.json +3 -1
  332. package/validation/tests/jobProperties/targetsNear.json +237 -0
  333. package/validation/tests/jobProperties/textNodes.json +29 -29
  334. package/validation/tests/jobProperties/textSem.json +26 -1
  335. package/validation/tests/jobProperties/title.json +12 -2
  336. package/validation/tests/jobProperties/titledEl.json +39 -9
  337. package/validation/tests/jobProperties/userPath.json +317 -0
  338. package/validation/tests/jobProperties/zIndex.json +40 -35
  339. package/validation/tests/targets/allCapStyle/index.html +26 -0
  340. package/validation/tests/targets/checkpoint/index.html +38 -0
  341. package/validation/tests/targets/datalistRef/index.html +1 -1
  342. package/validation/tests/targets/focAndOp/bad.html +29 -0
  343. package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
  344. package/validation/tests/targets/focInd/bad.html +2 -1
  345. package/validation/tests/targets/headEl/index.html +10 -1
  346. package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
  347. package/validation/tests/targets/userPath/index.html +42 -0
  348. package/validation/validateTest.js +62 -10
  349. package/.claude/settings.local.json +0 -11
  350. package/.eslintrc.json +0 -41
  351. package/htmlcs/.eslintrc.json +0 -67
  352. package/memory/MEMORY.md +0 -3
  353. package/memory/project_validation_pause.md +0 -10
  354. package/validation/tests/jobProperties/linkTitle.json +0 -127
  355. package/validation/tests/jobProperties/opFoc.json +0 -164
  356. package/validation/tests/jobProperties/targetSmall.json +0 -152
  357. package/validation/tests/jobProperties/targetTiny.json +0 -142
  358. package/validation/tests/targets/focOp/bad.html +0 -25
  359. package/validation/tests/targets/linkTitle/index.html +0 -24
  360. package/validation/tests/targets/opFoc/bad.html +0 -26
  361. package/validation/tests/targets/opFoc/good.html +0 -23
package/tests/pour.js ADDED
@@ -0,0 +1,242 @@
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
+ pour
51
+ Implements the Pour Engine (https://github.com/pourdev/pour-engine) WCAG 2.2 ruleset.
52
+ Compiled to pour.js by tsc (issue #73); edit this file, not the emitted one.
53
+
54
+ Pour ships as ES modules with no dist build, so a vendored IIFE bundle is expected at
55
+ ../pour/pour.min.js, exposing a `pourEngine` global with {run, name, version}. See
56
+ ../pour/README.md for the build command and the pinned upstream commit.
57
+
58
+ Instance policy (engine-candidacy-pipeline.md, Stage 1): `violations` become instances with
59
+ outcome `failed` at ordinalSeverity 2-3; `incomplete` become instances with outcome `cantTell`
60
+ at 0-1; `passes`, `inapplicable`, and `manualReview` are tallied in `data` only and never
61
+ become instances.
62
+ */
63
+ // CONSTANTS
64
+ // Severity weights within a certainty band, mirroring tests/axe.ts.
65
+ const severityWeights = {
66
+ minor: 0,
67
+ moderate: 0,
68
+ serious: 1,
69
+ critical: 1
70
+ };
71
+ // FUNCTIONS
72
+ // Performs and reports the Pour Engine tests.
73
+ const reporter = async (page, report, actIndex) => {
74
+ // Get the nonce, if any.
75
+ const act = report.acts[actIndex];
76
+ const { jobData } = report;
77
+ const scriptNonce = (jobData && jobData.lastScriptNonce);
78
+ // Initialize the act report.
79
+ let data = {};
80
+ const result = {
81
+ nativeResult: {},
82
+ standardResult: {}
83
+ };
84
+ const standard = report.standard !== 'no';
85
+ // If standard results are to be reported:
86
+ if (standard) {
87
+ // Initialize the standard result.
88
+ result.standardResult = (0, standard_1.getStandardResult)();
89
+ }
90
+ // Get the vendored tool bundle.
91
+ let script;
92
+ try {
93
+ script = await fs.readFile(`${__dirname}/../pour/pour.min.js`, 'utf8');
94
+ }
95
+ catch (error) {
96
+ data.prevented = true;
97
+ data.error = 'ERROR: pour bundle missing (see pour/README.md for vendoring instructions)';
98
+ return { data, result };
99
+ }
100
+ // Perform the tests and populate the native result.
101
+ result.nativeResult = await page.evaluate(args => new Promise(async (resolve) => {
102
+ const { scriptNonce, script } = args;
103
+ // Detect page-CSP blocking of the injection (the violation event is
104
+ // dispatched as a task, so the check below waits a tick).
105
+ let cspBlocked = false;
106
+ document.addEventListener('securitypolicyviolation', event => {
107
+ if ((event.violatedDirective || '').startsWith('script-src')) {
108
+ cspBlocked = true;
109
+ }
110
+ });
111
+ // Add the tool script to the page.
112
+ const toolScript = document.createElement('script');
113
+ if (scriptNonce) {
114
+ toolScript.nonce = scriptNonce;
115
+ console.log(`Added nonce ${scriptNonce} to tool script`);
116
+ }
117
+ toolScript.textContent = script;
118
+ document.body.insertAdjacentElement('beforeend', toolScript);
119
+ // If the bundle failed to define its global:
120
+ if (typeof pourEngine === 'undefined' || typeof pourEngine.run !== 'function') {
121
+ // Let a pending securitypolicyviolation event fire before diagnosing.
122
+ await new Promise(tick => setTimeout(tick, 0));
123
+ // A CSP block is a per-page condition, not a browser fault — report it
124
+ // distinctly (wedge canaries must not match this message).
125
+ resolve({
126
+ prevented: true,
127
+ error: cspBlocked
128
+ ? 'pour injection blocked by page CSP (script-src)'
129
+ : `pourEngine global not defined (contentType ${document.contentType}, `
130
+ + `scriptChars ${script.length}, readyState ${document.readyState})`
131
+ });
132
+ return;
133
+ }
134
+ try {
135
+ // Run the engine on the top document. An empty tag selection runs every
136
+ // rule (all WCAG levels incl. AAA, plus pour's best-practice extras) —
137
+ // matching the axe adapter's kitchen-sink default; testilo's taxonomy
138
+ // decides per rule what counts downstream.
139
+ const pourReport = await pourEngine.run(document, {});
140
+ // Normalizes one certainty bucket (axe-style rule entries) to flat per-element findings.
141
+ const flatten = (bucket) => (bucket || [])
142
+ .flatMap(entry => {
143
+ // Criterion tag (e.g. wcag143) → dotted criterion (1.4.3).
144
+ const criterionTag = (entry.tags || []).find(tag => /^wcag\d{3,4}$/.test(tag));
145
+ const digits = criterionTag ? criterionTag.slice(4) : '';
146
+ const wcag = digits ? `${digits[0]}.${digits[1]}.${digits.slice(2)}` : '';
147
+ return (entry.nodes || []).map(node => {
148
+ const cssPath = Array.isArray(node.target) ? node.target[0] : '';
149
+ let xPath = '';
150
+ try {
151
+ const element = cssPath && document.querySelector(cssPath);
152
+ if (element && typeof window.getXPath === 'function') {
153
+ xPath = window.getXPath(element) ?? '';
154
+ }
155
+ }
156
+ catch (error) {
157
+ // Unresolvable path: instance falls back to excerpt only.
158
+ }
159
+ return {
160
+ ruleID: entry.id || '',
161
+ what: (entry.help || '').replace(/\s+/g, ' ').trim(),
162
+ wcag,
163
+ severity: (entry.impact || 'moderate').toLowerCase(),
164
+ html: (node.html || '').slice(0, 500),
165
+ cssPath,
166
+ xPath
167
+ };
168
+ });
169
+ });
170
+ resolve({
171
+ engineVersion: pourReport.testEngine && pourReport.testEngine.version || '',
172
+ violations: flatten(pourReport.violations),
173
+ incomplete: flatten(pourReport.incomplete),
174
+ // Tallied only; never instances. Passes serialize no nodes upstream;
175
+ // nodeCount carries the per-rule passing-element total.
176
+ passRuleCount: (pourReport.passes || []).length,
177
+ passElementCount: (pourReport.passes || [])
178
+ .reduce((total, entry) => total + (entry.nodeCount || 0), 0),
179
+ inapplicableRuleCount: (pourReport.inapplicable || []).length,
180
+ manualReviewCriterionCount: (pourReport.manualReview || []).length
181
+ });
182
+ }
183
+ catch (error) {
184
+ resolve({
185
+ prevented: true,
186
+ error: error.message
187
+ });
188
+ }
189
+ ;
190
+ }), {
191
+ scriptNonce,
192
+ script
193
+ });
194
+ const { nativeResult } = result;
195
+ // If the tool ran and rules were selected:
196
+ if (!nativeResult.prevented && act.rules) {
197
+ // Remove results of other rules.
198
+ const { rules } = act;
199
+ nativeResult.violations = (nativeResult.violations || [])
200
+ .filter(finding => rules.includes(finding.ruleID));
201
+ nativeResult.incomplete = (nativeResult.incomplete || [])
202
+ .filter(finding => rules.includes(finding.ruleID));
203
+ }
204
+ // If the tool was prevented from running:
205
+ if (nativeResult.prevented) {
206
+ data.prevented = true;
207
+ data.error = nativeResult.error;
208
+ }
209
+ // Otherwise, if a standard result is to be reported:
210
+ else if (standard) {
211
+ const { standardResult } = result;
212
+ // Record the tallied-only bucket sizes.
213
+ data.passRuleCount = nativeResult.passRuleCount;
214
+ data.passElementCount = nativeResult.passElementCount;
215
+ data.inapplicableRuleCount = nativeResult.inapplicableRuleCount;
216
+ data.manualReviewCriterionCount = nativeResult.manualReviewCriterionCount;
217
+ // For each certainty band:
218
+ const bands = [
219
+ [nativeResult.incomplete || [], 0, 'cantTell'],
220
+ [nativeResult.violations || [], 2, 'failed']
221
+ ];
222
+ bands.forEach(([findings, baseSeverity, outcome]) => {
223
+ // For each native-result finding:
224
+ findings.forEach(finding => {
225
+ // Add a standard-result instance.
226
+ const { ruleID, what, severity, xPath } = finding;
227
+ (0, standard_1.addInstance)(standardResult, {
228
+ ruleID,
229
+ what,
230
+ ordinalSeverity: baseSeverity + (severityWeights[severity] ?? 0),
231
+ outcome,
232
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
233
+ });
234
+ });
235
+ });
236
+ }
237
+ return {
238
+ data,
239
+ result
240
+ };
241
+ };
242
+ exports.reporter = reporter;
package/tests/pour.ts ADDED
@@ -0,0 +1,273 @@
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} from '../procs/standard';
16
+ import type {Act, Outcome, Report, StandardResult} from '../types';
17
+
18
+ // TYPES
19
+
20
+ // The pour-act properties this reporter consumes.
21
+ interface PourAct extends Act {
22
+ rules?: string[];
23
+ }
24
+ // A pour severity (axe-style impact).
25
+ type PourSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
26
+ // One element finding, flattened from the engine's per-rule results.
27
+ interface PourFinding {
28
+ ruleID: string;
29
+ what: string;
30
+ wcag: string;
31
+ severity: PourSeverity;
32
+ html: string;
33
+ cssPath: string;
34
+ xPath: string;
35
+ }
36
+ // The native result: flattened findings and tallies, or a prevention report.
37
+ interface PourNativeResult {
38
+ engineVersion?: string;
39
+ violations?: PourFinding[];
40
+ incomplete?: PourFinding[];
41
+ passRuleCount?: number;
42
+ passElementCount?: number;
43
+ inapplicableRuleCount?: number;
44
+ manualReviewCriterionCount?: number;
45
+ prevented?: boolean;
46
+ error?: string;
47
+ }
48
+ // One per-rule entry of a pour result bucket (verified against pour-engine 1.37.0).
49
+ interface PourRuleEntry {
50
+ id?: string;
51
+ help?: string;
52
+ impact?: string;
53
+ tags?: string[];
54
+ nodeCount?: number;
55
+ nodes?: {target?: string[]; html?: string}[];
56
+ }
57
+ // The pour report.
58
+ interface PourReport {
59
+ testEngine?: {version?: string};
60
+ violations?: PourRuleEntry[];
61
+ incomplete?: PourRuleEntry[];
62
+ passes?: PourRuleEntry[];
63
+ inapplicable?: PourRuleEntry[];
64
+ manualReview?: unknown[];
65
+ }
66
+
67
+ /*
68
+ The pourEngine global that the vendored bundle defines in the page. Declared without a
69
+ value, so this declaration is erased at emit and the references inside the page.evaluate
70
+ callback remain bare page-global references.
71
+ */
72
+ declare const pourEngine: {
73
+ run: (context: Document, options: Record<string, unknown>) => Promise<PourReport>;
74
+ };
75
+
76
+ /*
77
+ pour
78
+ Implements the Pour Engine (https://github.com/pourdev/pour-engine) WCAG 2.2 ruleset.
79
+ Compiled to pour.js by tsc (issue #73); edit this file, not the emitted one.
80
+
81
+ Pour ships as ES modules with no dist build, so a vendored IIFE bundle is expected at
82
+ ../pour/pour.min.js, exposing a `pourEngine` global with {run, name, version}. See
83
+ ../pour/README.md for the build command and the pinned upstream commit.
84
+
85
+ Instance policy (engine-candidacy-pipeline.md, Stage 1): `violations` become instances with
86
+ outcome `failed` at ordinalSeverity 2-3; `incomplete` become instances with outcome `cantTell`
87
+ at 0-1; `passes`, `inapplicable`, and `manualReview` are tallied in `data` only and never
88
+ become instances.
89
+ */
90
+
91
+ // CONSTANTS
92
+
93
+ // Severity weights within a certainty band, mirroring tests/axe.ts.
94
+ const severityWeights: Record<PourSeverity, number> = {
95
+ minor: 0,
96
+ moderate: 0,
97
+ serious: 1,
98
+ critical: 1
99
+ };
100
+
101
+ // FUNCTIONS
102
+
103
+ // Performs and reports the Pour Engine tests.
104
+ export const reporter = async (page: Page, report: Report, actIndex: number) => {
105
+ // Get the nonce, if any.
106
+ const act = report.acts[actIndex] as PourAct;
107
+ const {jobData} = report;
108
+ const scriptNonce = (jobData && jobData.lastScriptNonce) as string | undefined;
109
+ // Initialize the act report.
110
+ let data: Record<string, unknown> = {};
111
+ const result: {nativeResult: PourNativeResult; standardResult: StandardResult} = {
112
+ nativeResult: {},
113
+ standardResult: {}
114
+ };
115
+ const standard = report.standard !== 'no';
116
+ // If standard results are to be reported:
117
+ if (standard) {
118
+ // Initialize the standard result.
119
+ result.standardResult = getStandardResult();
120
+ }
121
+ // Get the vendored tool bundle.
122
+ let script: string;
123
+ try {
124
+ script = await fs.readFile(`${__dirname}/../pour/pour.min.js`, 'utf8');
125
+ }
126
+ catch(error) {
127
+ data.prevented = true;
128
+ data.error = 'ERROR: pour bundle missing (see pour/README.md for vendoring instructions)';
129
+ return {data, result};
130
+ }
131
+ // Perform the tests and populate the native result.
132
+ result.nativeResult = await page.evaluate(args => new Promise<PourNativeResult>(
133
+ async resolve => {
134
+ const {scriptNonce, script} = args;
135
+ // Detect page-CSP blocking of the injection (the violation event is
136
+ // dispatched as a task, so the check below waits a tick).
137
+ let cspBlocked = false;
138
+ document.addEventListener('securitypolicyviolation', event => {
139
+ if ((event.violatedDirective || '').startsWith('script-src')) {
140
+ cspBlocked = true;
141
+ }
142
+ });
143
+ // Add the tool script to the page.
144
+ const toolScript = document.createElement('script');
145
+ if (scriptNonce) {
146
+ toolScript.nonce = scriptNonce;
147
+ console.log(`Added nonce ${scriptNonce} to tool script`);
148
+ }
149
+ toolScript.textContent = script;
150
+ document.body.insertAdjacentElement('beforeend', toolScript);
151
+ // If the bundle failed to define its global:
152
+ if (typeof pourEngine === 'undefined' || typeof pourEngine.run !== 'function') {
153
+ // Let a pending securitypolicyviolation event fire before diagnosing.
154
+ await new Promise(tick => setTimeout(tick, 0));
155
+ // A CSP block is a per-page condition, not a browser fault — report it
156
+ // distinctly (wedge canaries must not match this message).
157
+ resolve({
158
+ prevented: true,
159
+ error: cspBlocked
160
+ ? 'pour injection blocked by page CSP (script-src)'
161
+ : `pourEngine global not defined (contentType ${document.contentType}, `
162
+ + `scriptChars ${script.length}, readyState ${document.readyState})`
163
+ });
164
+ return;
165
+ }
166
+ try {
167
+ // Run the engine on the top document. An empty tag selection runs every
168
+ // rule (all WCAG levels incl. AAA, plus pour's best-practice extras) —
169
+ // matching the axe adapter's kitchen-sink default; testilo's taxonomy
170
+ // decides per rule what counts downstream.
171
+ const pourReport = await pourEngine.run(document, {});
172
+ // Normalizes one certainty bucket (axe-style rule entries) to flat per-element findings.
173
+ const flatten = (bucket: PourRuleEntry[] | undefined): PourFinding[] => (bucket || [])
174
+ .flatMap(entry => {
175
+ // Criterion tag (e.g. wcag143) → dotted criterion (1.4.3).
176
+ const criterionTag = (entry.tags || []).find(tag => /^wcag\d{3,4}$/.test(tag));
177
+ const digits = criterionTag ? criterionTag.slice(4) : '';
178
+ const wcag = digits ? `${digits[0]}.${digits[1]}.${digits.slice(2)}` : '';
179
+ return (entry.nodes || []).map(node => {
180
+ const cssPath = Array.isArray(node.target) ? node.target[0] : '';
181
+ let xPath = '';
182
+ try {
183
+ const element = cssPath && document.querySelector(cssPath);
184
+ if (element && typeof window.getXPath === 'function') {
185
+ xPath = window.getXPath(element) ?? '';
186
+ }
187
+ }
188
+ catch(error) {
189
+ // Unresolvable path: instance falls back to excerpt only.
190
+ }
191
+ return {
192
+ ruleID: entry.id || '',
193
+ what: (entry.help || '').replace(/\s+/g, ' ').trim(),
194
+ wcag,
195
+ severity: (entry.impact || 'moderate').toLowerCase() as PourSeverity,
196
+ html: (node.html || '').slice(0, 500),
197
+ cssPath,
198
+ xPath
199
+ };
200
+ });
201
+ });
202
+ resolve({
203
+ engineVersion: pourReport.testEngine && pourReport.testEngine.version || '',
204
+ violations: flatten(pourReport.violations),
205
+ incomplete: flatten(pourReport.incomplete),
206
+ // Tallied only; never instances. Passes serialize no nodes upstream;
207
+ // nodeCount carries the per-rule passing-element total.
208
+ passRuleCount: (pourReport.passes || []).length,
209
+ passElementCount: (pourReport.passes || [])
210
+ .reduce((total, entry) => total + (entry.nodeCount || 0), 0),
211
+ inapplicableRuleCount: (pourReport.inapplicable || []).length,
212
+ manualReviewCriterionCount: (pourReport.manualReview || []).length
213
+ });
214
+ }
215
+ catch(error) {
216
+ resolve({
217
+ prevented: true,
218
+ error: (error as Error).message
219
+ });
220
+ };
221
+ }
222
+ ), {
223
+ scriptNonce,
224
+ script
225
+ });
226
+ const {nativeResult} = result;
227
+ // If the tool ran and rules were selected:
228
+ if (! nativeResult.prevented && act.rules) {
229
+ // Remove results of other rules.
230
+ const {rules} = act;
231
+ nativeResult.violations = (nativeResult.violations || [])
232
+ .filter(finding => rules.includes(finding.ruleID));
233
+ nativeResult.incomplete = (nativeResult.incomplete || [])
234
+ .filter(finding => rules.includes(finding.ruleID));
235
+ }
236
+ // If the tool was prevented from running:
237
+ if (nativeResult.prevented) {
238
+ data.prevented = true;
239
+ data.error = nativeResult.error;
240
+ }
241
+ // Otherwise, if a standard result is to be reported:
242
+ else if (standard) {
243
+ const {standardResult} = result;
244
+ // Record the tallied-only bucket sizes.
245
+ data.passRuleCount = nativeResult.passRuleCount;
246
+ data.passElementCount = nativeResult.passElementCount;
247
+ data.inapplicableRuleCount = nativeResult.inapplicableRuleCount;
248
+ data.manualReviewCriterionCount = nativeResult.manualReviewCriterionCount;
249
+ // For each certainty band:
250
+ const bands: [PourFinding[], number, Outcome][] = [
251
+ [nativeResult.incomplete || [], 0, 'cantTell'],
252
+ [nativeResult.violations || [], 2, 'failed']
253
+ ];
254
+ bands.forEach(([findings, baseSeverity, outcome]) => {
255
+ // For each native-result finding:
256
+ findings.forEach(finding => {
257
+ // Add a standard-result instance.
258
+ const {ruleID, what, severity, xPath} = finding;
259
+ addInstance(standardResult, {
260
+ ruleID,
261
+ what,
262
+ ordinalSeverity: baseSeverity + (severityWeights[severity] ?? 0),
263
+ outcome,
264
+ catalogIndex: getXPathCatalogIndex(report, xPath)
265
+ });
266
+ });
267
+ });
268
+ }
269
+ return {
270
+ data,
271
+ result
272
+ };
273
+ };
@@ -0,0 +1,39 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Report, StandardResult } from '../types';
3
+ interface QwElement {
4
+ htmlCode?: string;
5
+ }
6
+ interface QwRaResult {
7
+ verdict: string;
8
+ description?: string;
9
+ elements?: QwElement[];
10
+ }
11
+ interface QwRuleAssertions {
12
+ metadata?: {
13
+ warning?: number;
14
+ failed?: number;
15
+ };
16
+ results: QwRaResult[];
17
+ }
18
+ interface QwModuleReport {
19
+ assertions?: Record<string, QwRuleAssertions>;
20
+ }
21
+ interface QwNativeResult {
22
+ system?: {
23
+ page?: {
24
+ dom?: unknown;
25
+ };
26
+ };
27
+ modules?: Record<string, QwModuleReport>;
28
+ }
29
+ export declare const reporter: (page: Page, report: Report, actIndex: number, timeLimit: number) => Promise<{
30
+ data: {
31
+ prevented?: boolean;
32
+ error?: string;
33
+ };
34
+ result: {
35
+ nativeResult: QwNativeResult;
36
+ standardResult: StandardResult;
37
+ };
38
+ }>;
39
+ export {};