testaro 78.0.8 → 78.6.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 (357) hide show
  1. package/.gitattributes +94 -0
  2. package/.github/workflows/ci.yml +58 -0
  3. package/.github/workflows/publish.yml +50 -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 +143 -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/dist/nameComputation.js +1 -0
  16. package/docker-compose.yml +8 -2
  17. package/docs/checkpoint-scanning.md +199 -0
  18. package/docs/standard-result-outcome.md +165 -0
  19. package/env.example +49 -10
  20. package/eslint.config.mjs +101 -0
  21. package/netWatch.js +372 -222
  22. package/package.json +46 -31
  23. package/pour/README.md +47 -0
  24. package/pour/pour.min.js +8 -0
  25. package/procs/actDo.js +808 -0
  26. package/procs/catalog.d.ts +17 -0
  27. package/procs/catalog.js +312 -221
  28. package/procs/catalog.ts +398 -0
  29. package/procs/checkpoint.js +110 -0
  30. package/procs/config.d.ts +41 -0
  31. package/procs/config.js +98 -0
  32. package/procs/dateTime.js +2 -1
  33. package/procs/doActs.js +442 -849
  34. package/procs/doTestAct.js +27 -121
  35. package/procs/flow.js +221 -0
  36. package/procs/generateRuleRegistry.js +94 -0
  37. package/procs/getSource.d.ts +26 -0
  38. package/procs/job.js +115 -1
  39. package/procs/launch.d.ts +40 -0
  40. package/procs/launch.js +243 -108
  41. package/procs/nu.d.ts +48 -0
  42. package/procs/scope.js +196 -0
  43. package/procs/shoot.d.ts +33 -0
  44. package/procs/standard.d.ts +18 -0
  45. package/procs/standard.js +83 -0
  46. package/procs/standard.ts +126 -0
  47. package/procs/testAct.js +145 -0
  48. package/procs/testaro.d.ts +20 -0
  49. package/procs/testaro.js +236 -189
  50. package/procs/testaro.ts +321 -0
  51. package/procs/userPath.js +126 -0
  52. package/procs/xPath.d.ts +4 -0
  53. package/procs/xPath.js +81 -69
  54. package/procs/xPath.ts +106 -0
  55. package/procs/xPathScript.d.ts +6 -0
  56. package/procs/xPathScript.js +70 -0
  57. package/run.js +3 -2
  58. package/surea11y/README.md +44 -0
  59. package/surea11y/surea11y.browser.js +14 -0
  60. package/testaro/adbID.d.ts +3 -0
  61. package/testaro/adbID.js +38 -41
  62. package/testaro/adbID.ts +66 -0
  63. package/testaro/allCapStyle.d.ts +3 -0
  64. package/testaro/allCapStyle.js +31 -34
  65. package/testaro/allCapStyle.ts +55 -0
  66. package/testaro/allCaps.d.ts +16 -0
  67. package/testaro/allCaps.js +179 -151
  68. package/testaro/allCaps.ts +228 -0
  69. package/testaro/allHidden.d.ts +13 -0
  70. package/testaro/allHidden.js +29 -30
  71. package/testaro/allHidden.ts +50 -0
  72. package/testaro/allSlanted.d.ts +3 -0
  73. package/testaro/allSlanted.js +30 -33
  74. package/testaro/allSlanted.ts +54 -0
  75. package/testaro/altScheme.d.ts +3 -0
  76. package/testaro/altScheme.js +27 -30
  77. package/testaro/altScheme.ts +50 -0
  78. package/testaro/attVal.d.ts +3 -0
  79. package/testaro/attVal.js +20 -21
  80. package/testaro/attVal.ts +52 -0
  81. package/testaro/autocomplete.d.ts +3 -0
  82. package/testaro/autocomplete.js +59 -75
  83. package/testaro/autocomplete.ts +101 -0
  84. package/testaro/bulk.d.ts +13 -0
  85. package/testaro/bulk.js +32 -33
  86. package/testaro/bulk.ts +55 -0
  87. package/testaro/buttonMenu.d.ts +9 -0
  88. package/testaro/buttonMenu.js +317 -319
  89. package/testaro/buttonMenu.ts +391 -0
  90. package/testaro/captionLoc.d.ts +3 -0
  91. package/testaro/captionLoc.js +17 -20
  92. package/testaro/captionLoc.ts +40 -0
  93. package/testaro/datalistRef.d.ts +3 -0
  94. package/testaro/datalistRef.js +33 -36
  95. package/testaro/datalistRef.ts +55 -0
  96. package/testaro/distortion.d.ts +3 -0
  97. package/testaro/distortion.js +57 -26
  98. package/testaro/distortion.ts +81 -0
  99. package/testaro/docType.d.ts +15 -0
  100. package/testaro/docType.js +25 -25
  101. package/testaro/docType.ts +45 -0
  102. package/testaro/dupAtt.d.ts +16 -0
  103. package/testaro/dupAtt.js +113 -104
  104. package/testaro/dupAtt.ts +144 -0
  105. package/testaro/elements.d.ts +6 -0
  106. package/testaro/elements.js +153 -153
  107. package/testaro/elements.ts +215 -0
  108. package/testaro/embAc.d.ts +3 -0
  109. package/testaro/embAc.js +19 -20
  110. package/testaro/embAc.ts +40 -0
  111. package/testaro/focAll.d.ts +13 -0
  112. package/testaro/focAll.js +185 -191
  113. package/testaro/focAll.ts +217 -0
  114. package/testaro/focAndOp.d.ts +3 -0
  115. package/testaro/focAndOp.js +100 -104
  116. package/testaro/focAndOp.ts +128 -0
  117. package/testaro/focInd.d.ts +3 -0
  118. package/testaro/focInd.js +64 -66
  119. package/testaro/focInd.ts +96 -0
  120. package/testaro/focVis.d.ts +3 -0
  121. package/testaro/focVis.js +28 -30
  122. package/testaro/focVis.ts +52 -0
  123. package/testaro/headEl.d.ts +10 -0
  124. package/testaro/headEl.js +61 -62
  125. package/testaro/headEl.ts +82 -0
  126. package/testaro/headingAmb.d.ts +3 -0
  127. package/testaro/headingAmb.js +54 -63
  128. package/testaro/headingAmb.ts +72 -0
  129. package/testaro/hovInd.d.ts +12 -0
  130. package/testaro/hovInd.js +163 -130
  131. package/testaro/hovInd.ts +199 -0
  132. package/testaro/hover.d.ts +3 -0
  133. package/testaro/hover.js +155 -126
  134. package/testaro/hover.ts +154 -0
  135. package/testaro/hr.d.ts +3 -0
  136. package/testaro/hr.js +14 -17
  137. package/testaro/hr.ts +36 -0
  138. package/testaro/imageLink.d.ts +3 -0
  139. package/testaro/imageLink.js +17 -20
  140. package/testaro/imageLink.ts +42 -0
  141. package/testaro/labClash.d.ts +3 -0
  142. package/testaro/labClash.js +31 -33
  143. package/testaro/labClash.ts +54 -0
  144. package/testaro/legendLoc.d.ts +3 -0
  145. package/testaro/legendLoc.js +17 -20
  146. package/testaro/legendLoc.ts +42 -0
  147. package/testaro/lineHeight.d.ts +3 -0
  148. package/testaro/lineHeight.js +43 -48
  149. package/testaro/lineHeight.ts +70 -0
  150. package/testaro/linkAmb.d.ts +7 -0
  151. package/testaro/linkAmb.js +79 -80
  152. package/testaro/linkAmb.ts +105 -0
  153. package/testaro/linkExt.d.ts +3 -0
  154. package/testaro/linkExt.js +13 -16
  155. package/testaro/linkExt.ts +35 -0
  156. package/testaro/linkOldAtt.d.ts +3 -0
  157. package/testaro/linkOldAtt.js +25 -28
  158. package/testaro/linkOldAtt.ts +48 -0
  159. package/testaro/linkTo.d.ts +3 -0
  160. package/testaro/linkTo.js +22 -22
  161. package/testaro/linkTo.ts +43 -0
  162. package/testaro/linkUl.d.ts +3 -0
  163. package/testaro/linkUl.js +30 -34
  164. package/testaro/linkUl.ts +54 -0
  165. package/testaro/miniText.d.ts +3 -0
  166. package/testaro/miniText.js +41 -44
  167. package/testaro/miniText.ts +68 -0
  168. package/testaro/motion.d.ts +10 -0
  169. package/testaro/motion.js +92 -96
  170. package/testaro/motion.ts +125 -0
  171. package/testaro/nonTable.d.ts +3 -0
  172. package/testaro/nonTable.js +39 -45
  173. package/testaro/nonTable.ts +66 -0
  174. package/testaro/optRoleSel.d.ts +3 -0
  175. package/testaro/optRoleSel.js +16 -19
  176. package/testaro/optRoleSel.ts +41 -0
  177. package/testaro/phOnly.d.ts +3 -0
  178. package/testaro/phOnly.js +18 -21
  179. package/testaro/phOnly.ts +43 -0
  180. package/testaro/pseudoP.d.ts +3 -0
  181. package/testaro/pseudoP.js +36 -38
  182. package/testaro/pseudoP.ts +59 -0
  183. package/testaro/radioSet.d.ts +3 -0
  184. package/testaro/radioSet.js +57 -59
  185. package/testaro/radioSet.ts +79 -0
  186. package/testaro/registry.d.ts +63 -0
  187. package/testaro/registry.js +67 -0
  188. package/testaro/registry.ts +141 -0
  189. package/testaro/role.d.ts +3 -0
  190. package/testaro/role.js +27 -29
  191. package/testaro/role.ts +53 -0
  192. package/testaro/secHeading.d.ts +3 -0
  193. package/testaro/secHeading.js +30 -33
  194. package/testaro/secHeading.ts +53 -0
  195. package/testaro/styleDiff.d.ts +25 -0
  196. package/testaro/styleDiff.js +248 -252
  197. package/testaro/styleDiff.ts +303 -0
  198. package/testaro/tabNav.d.ts +33 -0
  199. package/testaro/tabNav.js +272 -342
  200. package/testaro/tabNav.ts +454 -0
  201. package/testaro/targetsNear.d.ts +9 -0
  202. package/testaro/targetsNear.js +130 -132
  203. package/testaro/targetsNear.ts +160 -0
  204. package/testaro/textNodes.d.ts +6 -0
  205. package/testaro/textNodes.js +139 -135
  206. package/testaro/textNodes.ts +185 -0
  207. package/testaro/textSem.d.ts +3 -0
  208. package/testaro/textSem.js +25 -28
  209. package/testaro/textSem.ts +47 -0
  210. package/testaro/title.d.ts +9 -0
  211. package/testaro/title.js +16 -13
  212. package/testaro/title.ts +31 -0
  213. package/testaro/titledEl.d.ts +3 -0
  214. package/testaro/titledEl.js +15 -18
  215. package/testaro/titledEl.ts +38 -0
  216. package/testaro/zIndex.d.ts +3 -0
  217. package/testaro/zIndex.js +19 -22
  218. package/testaro/zIndex.ts +42 -0
  219. package/tests/alfa.d.ts +45 -0
  220. package/tests/alfa.js +138 -141
  221. package/tests/alfa.ts +214 -0
  222. package/tests/aslint.d.ts +33 -0
  223. package/tests/aslint.js +272 -249
  224. package/tests/aslint.ts +301 -0
  225. package/tests/axe.d.ts +27 -0
  226. package/tests/axe.js +199 -200
  227. package/tests/axe.ts +277 -0
  228. package/tests/ed11y.d.ts +28 -0
  229. package/tests/ed11y.js +141 -99
  230. package/tests/ed11y.ts +178 -0
  231. package/tests/htmlcs.d.ts +21 -0
  232. package/tests/htmlcs.js +174 -140
  233. package/tests/htmlcs.ts +181 -0
  234. package/tests/ibm.d.ts +57 -0
  235. package/tests/ibm.js +182 -175
  236. package/tests/ibm.ts +287 -0
  237. package/tests/nuVal.d.ts +13 -0
  238. package/tests/nuVal.js +107 -112
  239. package/tests/nuVal.ts +145 -0
  240. package/tests/nuVnu.d.ts +15 -0
  241. package/tests/nuVnu.js +141 -111
  242. package/tests/nuVnu.ts +144 -0
  243. package/tests/pour.d.ts +31 -0
  244. package/tests/pour.js +242 -0
  245. package/tests/pour.ts +273 -0
  246. package/tests/qualWeb.d.ts +39 -0
  247. package/tests/qualWeb.js +262 -273
  248. package/tests/qualWeb.ts +344 -0
  249. package/tests/surea11y.d.ts +33 -0
  250. package/tests/surea11y.js +288 -0
  251. package/tests/surea11y.ts +334 -0
  252. package/tests/testaro.d.ts +25 -0
  253. package/tests/testaro.js +745 -652
  254. package/tests/testaro.ts +862 -0
  255. package/tests/wave.d.ts +46 -0
  256. package/tests/wave.js +166 -177
  257. package/tests/wave.ts +252 -0
  258. package/tsconfig.json +17 -0
  259. package/types.d.ts +243 -0
  260. package/types.js +10 -0
  261. package/types.ts +376 -0
  262. package/validation/act/README.md +46 -0
  263. package/validation/act/capture.js +877 -0
  264. package/validation/act/chromium-issue-draft.md +66 -0
  265. package/validation/act/fp-triage-2026-08-22.md +86 -0
  266. package/validation/act/isolation-notes.md +159 -0
  267. package/validation/act/playwright-issue-draft.md +73 -0
  268. package/validation/act/propose-mappings.js +0 -0
  269. package/validation/act/repro-cdp-raw.js +105 -0
  270. package/validation/act/repro-metarefresh.js +65 -0
  271. package/validation/act/score.js +184 -0
  272. package/validation/act/stage3a-stress-report.md +69 -0
  273. package/validation/act/stage3b-mapping-proposals.md +94 -0
  274. package/validation/act/stage3b-triage-draft.md +138 -0
  275. package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
  276. package/validation/executors/netWatch.js +180 -90
  277. package/validation/executors/test.js +17 -2
  278. package/validation/executors/tests.js +118 -10
  279. package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
  280. package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
  281. package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
  282. package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
  283. package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
  284. package/validation/knownFailures.json +1 -0
  285. package/validation/tests/jobProperties/adbID.json +27 -2
  286. package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
  287. package/validation/tests/jobProperties/allCaps.json +25 -0
  288. package/validation/tests/jobProperties/allHidden.json +133 -13
  289. package/validation/tests/jobProperties/allSlanted.json +3 -3
  290. package/validation/tests/jobProperties/altScheme.json +23 -8
  291. package/validation/tests/jobProperties/attVal.json +57 -57
  292. package/validation/tests/jobProperties/autocomplete.json +11 -1
  293. package/validation/tests/jobProperties/bulk.json +6 -1
  294. package/validation/tests/jobProperties/buttonMenu.json +63 -42
  295. package/validation/tests/jobProperties/captionLoc.json +1 -6
  296. package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
  297. package/validation/tests/jobProperties/checkpoint-page.json +397 -0
  298. package/validation/tests/jobProperties/checkpoint.json +395 -0
  299. package/validation/tests/jobProperties/datalistRef.json +20 -5
  300. package/validation/tests/jobProperties/distortion.json +28 -3
  301. package/validation/tests/jobProperties/docType.json +2 -2
  302. package/validation/tests/jobProperties/dupAtt.json +48 -33
  303. package/validation/tests/jobProperties/elements.json +28 -28
  304. package/validation/tests/jobProperties/embAc.json +36 -31
  305. package/validation/tests/jobProperties/focAndOp.json +284 -0
  306. package/validation/tests/jobProperties/focInd.json +34 -34
  307. package/validation/tests/jobProperties/focVis.json +1 -1
  308. package/validation/tests/jobProperties/hover.json +39 -37
  309. package/validation/tests/jobProperties/hr.json +13 -3
  310. package/validation/tests/jobProperties/imageLink.json +0 -5
  311. package/validation/tests/jobProperties/labClash.json +68 -33
  312. package/validation/tests/jobProperties/legendLoc.json +1 -6
  313. package/validation/tests/jobProperties/lineHeight.json +2 -2
  314. package/validation/tests/jobProperties/linkAmb.json +13 -18
  315. package/validation/tests/jobProperties/linkExt.json +1 -1
  316. package/validation/tests/jobProperties/linkOldAtt.json +12 -2
  317. package/validation/tests/jobProperties/linkTo.json +1 -1
  318. package/validation/tests/jobProperties/linkUl.json +65 -65
  319. package/validation/tests/jobProperties/miniText.json +7 -2
  320. package/validation/tests/jobProperties/motion.json +4 -50
  321. package/validation/tests/jobProperties/nonTable.json +48 -3
  322. package/validation/tests/jobProperties/optRoleSel.json +12 -2
  323. package/validation/tests/jobProperties/phOnly.json +6 -16
  324. package/validation/tests/jobProperties/pseudoP.json +17 -2
  325. package/validation/tests/jobProperties/radioSet.json +30 -30
  326. package/validation/tests/jobProperties/role.json +25 -5
  327. package/validation/tests/jobProperties/secHeading.json +26 -21
  328. package/validation/tests/jobProperties/styleDiff.json +35 -5
  329. package/validation/tests/jobProperties/tabNav.json +3 -1
  330. package/validation/tests/jobProperties/targetsNear.json +237 -0
  331. package/validation/tests/jobProperties/textNodes.json +29 -29
  332. package/validation/tests/jobProperties/textSem.json +26 -1
  333. package/validation/tests/jobProperties/title.json +12 -2
  334. package/validation/tests/jobProperties/titledEl.json +39 -9
  335. package/validation/tests/jobProperties/userPath.json +317 -0
  336. package/validation/tests/jobProperties/zIndex.json +40 -35
  337. package/validation/tests/targets/allCapStyle/index.html +26 -0
  338. package/validation/tests/targets/checkpoint/index.html +38 -0
  339. package/validation/tests/targets/datalistRef/index.html +1 -1
  340. package/validation/tests/targets/focAndOp/bad.html +29 -0
  341. package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
  342. package/validation/tests/targets/focInd/bad.html +2 -1
  343. package/validation/tests/targets/headEl/index.html +10 -1
  344. package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
  345. package/validation/tests/targets/userPath/index.html +42 -0
  346. package/validation/validateTest.js +61 -9
  347. package/.claude/settings.local.json +0 -11
  348. package/.eslintrc.json +0 -41
  349. package/htmlcs/.eslintrc.json +0 -67
  350. package/validation/tests/jobProperties/linkTitle.json +0 -127
  351. package/validation/tests/jobProperties/opFoc.json +0 -164
  352. package/validation/tests/jobProperties/targetSmall.json +0 -152
  353. package/validation/tests/jobProperties/targetTiny.json +0 -142
  354. package/validation/tests/targets/focOp/bad.html +0 -25
  355. package/validation/tests/targets/linkTitle/index.html +0 -24
  356. package/validation/tests/targets/opFoc/bad.html +0 -26
  357. 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
+ url?: string;
24
+ evaluation?: string;
25
+ };
26
+ modules?: Record<string, QwModuleReport>;
27
+ }
28
+ export declare const reporter: (page: Page, report: Report, actIndex: number, timeLimit: number) => Promise<{
29
+ data: {
30
+ prevented?: boolean;
31
+ error?: string;
32
+ rulePreventions?: Record<string, string>;
33
+ };
34
+ result: {
35
+ nativeResult: QwNativeResult;
36
+ standardResult: StandardResult;
37
+ };
38
+ }>;
39
+ export {};