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
package/tests/axe.ts ADDED
@@ -0,0 +1,277 @@
1
+ /*
2
+ © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jeff Witt.
4
+ © 2025–2026 Jonathan Robert Pool.
5
+
6
+ Licensed under the MIT License. See LICENSE file at the project root or
7
+ https://opensource.org/license/mit/ for details.
8
+
9
+ SPDX-License-Identifier: MIT
10
+ */
11
+
12
+ // IMPORTS
13
+
14
+ import type {Page} from 'playwright';
15
+ import type {AxeResults, NodeResult, RunOptions} from 'axe-core';
16
+ import {getAttributeXPath, getXPathCatalogIndex} from '../procs/xPath';
17
+ import {getStandardResult, addInstance} from '../procs/standard';
18
+ import type {Act, Report, StandardInstance, StandardResult} from '../types';
19
+ const axePlaywright = require('axe-playwright') as typeof import('axe-playwright');
20
+
21
+ // TYPES
22
+
23
+ // The axe-act properties this reporter consumes.
24
+ interface AxeAct extends Act {
25
+ detailLevel?: number;
26
+ rules?: string[];
27
+ }
28
+ // The impact-classified severity of an axe node.
29
+ type AxeImpact = 'minor' | 'moderate' | 'serious' | 'critical';
30
+ // The native result: rule and node totals and the axe report details.
31
+ interface AxeNativeResult {
32
+ totals?: {
33
+ rulesNA: number;
34
+ rulesPassed: number;
35
+ rulesWarned: number;
36
+ rulesViolated: number;
37
+ warnings: Record<AxeImpact, number>;
38
+ violations: Record<AxeImpact, number>;
39
+ };
40
+ details?: AxeResults;
41
+ }
42
+ // The data of the act report.
43
+ interface AxeData {
44
+ prevented?: boolean;
45
+ error?: string;
46
+ }
47
+
48
+ // CONSTANTS
49
+
50
+ const {injectAxe} = axePlaywright;
51
+ const severityWeights: Record<AxeImpact, number> = {
52
+ minor: 0,
53
+ moderate: 0,
54
+ serious: 1,
55
+ critical: 1
56
+ };
57
+
58
+ /*
59
+ axe
60
+ Implements the axe-core ruleset for accessibility.
61
+
62
+ The rules argument defaults to all rules; otherwise, specify an array of rule names.
63
+
64
+ The detailLevel argument specifies how many result categories are to be included in the
65
+ details. 0 = none; 1 = violations; 2 = violations and incomplete; 3 = violations, incomplete,
66
+ and passes; 4 = violations, incomplete, passes, and inapplicable. Regardless of the value of this
67
+ argument, Axe-core is instructed to report all nodes with violation or incomplete results, but
68
+ only 1 node per rule found to be passed or inapplicable. Therefore, from the results of this test
69
+ it is possible to count the rules passed and the inapplicable rules, but not the nodes for which
70
+ each rule is passed or inapplicable. To count those nodes, one would need to revise the
71
+ 'resultTypes' property of the 'axeOptions' object.
72
+
73
+ The report of this test shows rule totals by result category and, within the violation and
74
+ incomplete categories, node totals by severity. It does not show rule or node totals by test
75
+ category (“tag”), such as 'wcag21aaa'. Scoring can consider test categories by getting the value
76
+ of the 'tags' property of each rule.
77
+ Compiled to axe.js by tsc (issue #73); edit this file, not the emitted one.
78
+ */
79
+
80
+ // FUNCTIONS
81
+
82
+ // Conducts and reports the Axe tests.
83
+ export const reporter = async (page: Page, report: Report, actIndex: number) => {
84
+ const act = report.acts[actIndex] as AxeAct;
85
+ const {detailLevel, rules} = act;
86
+ // Initialize the act report.
87
+ let data: AxeData = {};
88
+ const result: {nativeResult: AxeNativeResult; standardResult: StandardResult} = {
89
+ nativeResult: {},
90
+ standardResult: {}
91
+ };
92
+ const standard = report.standard !== 'no';
93
+ // If standard results are to be reported:
94
+ if (standard) {
95
+ // Initialize the standard result.
96
+ result.standardResult = getStandardResult();
97
+ }
98
+ const {nativeResult, standardResult} = result;
99
+ // Inject axe-core into the page.
100
+ await injectAxe(page as Parameters<typeof injectAxe>[0])
101
+ .catch(error => {
102
+ console.log(`ERROR: Axe injection failed (${error.message})`);
103
+ data.prevented = true;
104
+ data.error = 'ERROR: axe injection failed';
105
+ });
106
+ // If the injection succeeded:
107
+ if (! data.prevented) {
108
+ // Get the data on the elements violating the specified axe-core rules.
109
+ const axeOptions: RunOptions = {
110
+ resultTypes: ['violations', 'incomplete']
111
+ };
112
+ if (rules && rules.length) {
113
+ // A bare array would be read as tag names; rule IDs need the object form.
114
+ axeOptions.runOnly = {type: 'rule', values: rules};
115
+ }
116
+ else {
117
+ axeOptions.runOnly = ['experimental', 'best-practice', 'wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa'];
118
+ }
119
+ // If the act is scoped to changed subtrees, test only them (axe's include context).
120
+ const scopeRoots = report.scope?.roots;
121
+ const context = scopeRoots && scopeRoots.length
122
+ ? {include: scopeRoots} as unknown as Parameters<typeof axePlaywright.getAxeResults>[1]
123
+ : null as unknown as undefined;
124
+ const axeReport = await axePlaywright.getAxeResults(
125
+ page as Parameters<typeof injectAxe>[0], context, axeOptions
126
+ );
127
+ const {inapplicable, passes, incomplete, violations} = axeReport;
128
+ // If the test succeeded:
129
+ if (violations) {
130
+ // Initialize the native result.
131
+ nativeResult.totals = {
132
+ rulesNA: 0,
133
+ rulesPassed: 0,
134
+ rulesWarned: 0,
135
+ rulesViolated: 0,
136
+ warnings: {
137
+ minor: 0,
138
+ moderate: 0,
139
+ serious: 0,
140
+ critical: 0
141
+ },
142
+ violations: {
143
+ minor: 0,
144
+ moderate: 0,
145
+ serious: 0,
146
+ critical: 0
147
+ }
148
+ };
149
+ nativeResult.details = axeReport;
150
+ // Populate the native-result totals.
151
+ const {totals} = nativeResult;
152
+ totals.rulesNA = inapplicable.length;
153
+ totals.rulesPassed = passes.length;
154
+ incomplete.forEach(rule => {
155
+ totals.rulesWarned++;
156
+ rule.nodes.forEach(node => {
157
+ totals.warnings[node.impact as AxeImpact]++;
158
+ });
159
+ });
160
+ violations.forEach(rule => {
161
+ totals.rulesViolated++;
162
+ rule.nodes.forEach(node => {
163
+ totals.violations[node.impact as AxeImpact]++;
164
+ });
165
+ });
166
+ // If standard results are to be reported and there are any suspicions:
167
+ if (standard && (totals.rulesViolated || totals.rulesWarned)) {
168
+ // Resolve each suspected element's FULL data-xpath from the live DOM,
169
+ // keyed by its axe target. axe-core truncates node.html (~300 chars,
170
+ // appends '...'), so parsing data-xpath out of node.html yields a
171
+ // truncated XPath for elements with long attribute lists. node.target
172
+ // is a reliable CSS selector; read the injected data-xpath directly off
173
+ // the element instead. Falls back to the node.html parse on any failure.
174
+ const fullXPathByTargetKey: Record<string, string> = {};
175
+ try {
176
+ const suspectNodes = (['incomplete', 'violations'] as const)
177
+ .filter(certainty => nativeResult?.details?.[certainty])
178
+ .flatMap(certainty => nativeResult.details![certainty].flatMap(rule => rule.nodes));
179
+ const targets = suspectNodes.map(node => node.target as unknown as string | string[]);
180
+ const resolvedXPaths = await page.evaluate(targetList => targetList.map(target => {
181
+ try {
182
+ // axe target is an array of selectors (nested arrays for frames/
183
+ // shadow roots). Use the deepest plain-string selector for the
184
+ // common, non-framed case; skip otherwise.
185
+ const selector = Array.isArray(target)
186
+ ? (typeof target[target.length - 1] === 'string' ? target[target.length - 1] : null)
187
+ : (typeof target === 'string' ? target : null);
188
+ if (! selector) {
189
+ return null;
190
+ }
191
+ const element = document.querySelector(selector);
192
+ return element ? element.getAttribute('data-xpath') : null;
193
+ }
194
+ catch(error) {
195
+ return null;
196
+ }
197
+ }), targets);
198
+ suspectNodes.forEach((node, index) => {
199
+ if (resolvedXPaths[index]) {
200
+ fullXPathByTargetKey[JSON.stringify(node.target)] = resolvedXPaths[index];
201
+ }
202
+ });
203
+ }
204
+ catch(error) {
205
+ // Leave the map empty; every instance falls back to node.html below.
206
+ }
207
+ // For each certainty type:
208
+ (['incomplete', 'violations'] as const).forEach(certainty => {
209
+ // If there are any suspicions of this type:
210
+ if (nativeResult?.details?.[certainty]) {
211
+ // For each rule with any suspicions:
212
+ nativeResult.details[certainty].forEach(rule => {
213
+ // For each element suspected of violating the rule:
214
+ rule.nodes.forEach((node: NodeResult) => {
215
+ // Get descriptions of the rule.
216
+ const whatSet = new Set([
217
+ rule.help,
218
+ ... node.any.map(anyItem => anyItem.message),
219
+ ... node.all.map(allItem => allItem.message)
220
+ ]);
221
+ // Get the ordinal severity of the suspicion.
222
+ const ordinalSeverity = severityWeights[node.impact as AxeImpact]
223
+ + (certainty === 'violations' ? 2 : 0);
224
+ // Get the XPath of the suspected element from its data-xpath
225
+ // attribute. Prefer the full value resolved from the live DOM
226
+ // (above); fall back to parsing it out of axe's node.html,
227
+ // which axe truncates and can corrupt the XPath.
228
+ const xPath = fullXPathByTargetKey[JSON.stringify(node.target)]
229
+ || getAttributeXPath(node.html);
230
+ addInstance(standardResult, {
231
+ ruleID: rule.id,
232
+ what: Array.from(whatSet.values()).join('; '),
233
+ ordinalSeverity,
234
+ outcome: certainty === 'violations' ? 'failed' : 'cantTell',
235
+ catalogIndex: getXPathCatalogIndex(report, xPath)
236
+ });
237
+ });
238
+ });
239
+ }
240
+ });
241
+ }
242
+ // Delete the properties of the report details that the detail level excludes, after the
243
+ // standard result has been made from them, so the standard result does not depend on
244
+ // the detail level.
245
+ const irrelevants = ['inapplicable', 'passes', 'incomplete', 'violations']
246
+ .slice(0, 4 - (detailLevel as number));
247
+ irrelevants.forEach(irrelevant => {
248
+ delete (axeReport as unknown as Record<string, unknown>)[irrelevant];
249
+ });
250
+ }
251
+ // Otherwise, i.e. if the test failed:
252
+ else {
253
+ // Report this.
254
+ data.prevented = true;
255
+ data.error = 'ERROR: Act failed';
256
+ if (standard) {
257
+ standardResult.prevented = true;
258
+ }
259
+ }
260
+ }
261
+ // Return the result.
262
+ try {
263
+ JSON.stringify(data);
264
+ }
265
+ catch(error) {
266
+ const message = `ERROR: Axe result cannot be made JSON (${(error as Error).message})`;
267
+ console.log(message);
268
+ data = {
269
+ prevented: true,
270
+ error: message
271
+ };
272
+ }
273
+ return {
274
+ data,
275
+ result
276
+ };
277
+ };
@@ -0,0 +1,28 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Report, StandardResult } from '../types';
3
+ interface Ed11yNativeInstance {
4
+ test: string;
5
+ content: string;
6
+ dismissalKey: string;
7
+ html: string;
8
+ xPath: string | null | undefined;
9
+ }
10
+ interface Ed11yNativeResult {
11
+ resultCount?: number;
12
+ errorCount?: number;
13
+ warningCount?: number;
14
+ results?: Ed11yNativeInstance[];
15
+ prevented?: boolean;
16
+ error?: string;
17
+ }
18
+ export declare const reporter: (page: Page, report: Report, actIndex: number) => Promise<{
19
+ data: {
20
+ prevented?: boolean;
21
+ error?: string;
22
+ };
23
+ result: {
24
+ nativeResult: Ed11yNativeResult;
25
+ standardResult: StandardResult;
26
+ };
27
+ }>;
28
+ export {};
package/tests/ed11y.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
4
  © 2026 Jeff Witt.
@@ -8,114 +9,155 @@
8
9
 
9
10
  SPDX-License-Identifier: MIT
10
11
  */
11
-
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.reporter = void 0;
47
+ // IMPORTS
48
+ const fs = __importStar(require("fs/promises"));
49
+ const xPath_1 = require("../procs/xPath");
50
+ const standard_1 = require("../procs/standard");
12
51
  /*
13
52
  ed11y
14
53
  Implements the Editoria11y ruleset for accessibility.
54
+ Compiled to ed11y.js by tsc (issue #73); edit this file, not the emitted one.
15
55
  */
16
-
17
- // IMPORTS
18
-
19
- const fs = require('fs/promises');
20
- const {getXPathCatalogIndex} = require('../procs/xPath');
21
-
22
56
  // FUNCTIONS
23
-
24
57
  // Performs and reports the Editoria11y tests.
25
- exports.reporter = async (page, report, actIndex) => {
26
- // Get the nonce, if any.
27
- const act = report.acts[actIndex];
28
- const {jobData} = report;
29
- const scriptNonce = jobData && jobData.lastScriptNonce;
30
- // Initialize the act report.
31
- let data = {};
32
- const result = {
33
- nativeResult: {},
34
- standardResult: {}
35
- };
36
- const standard = report.standard !== 'no';
37
- // If standard results are to be reported:
38
- if (standard) {
39
- // Initialize the standard result.
40
- result.standardResult = {
41
- prevented: false,
42
- totals: [0, 0, 0, 0],
43
- instances: []
58
+ const reporter = async (page, report, actIndex) => {
59
+ // Get the nonce, if any.
60
+ const act = report.acts[actIndex];
61
+ const { jobData } = report;
62
+ const scriptNonce = (jobData && jobData.lastScriptNonce);
63
+ // Initialize the act report.
64
+ let data = {};
65
+ const result = {
66
+ nativeResult: {},
67
+ standardResult: {}
44
68
  };
45
- }
46
- // Get the tool script.
47
- const script = await fs.readFile(`${__dirname}/../ed11y/editoria11y.min.js`, 'utf8');
48
- // Perform the specified tests and populate the native result.
49
- result.nativeResult = await page.evaluate(args => new Promise(async resolve => {
50
- const {scriptNonce, script, rulesToTest} = args;
51
- // When the script has been executed, creating data in an Ed11y object:
52
- document.addEventListener('ed11yResults', async () => {
53
- let {results} = Ed11y;
54
- // If rules were selected:
55
- if (rulesToTest) {
56
- // Remove results of other rules.
57
- results = results.filter(result => rulesToTest.includes(result.test));
58
- }
59
- // Return the native result.
60
- resolve({
61
- resultCount: results.length,
62
- errorCount: Ed11y.errorCount,
63
- warningCount: Ed11y.warningCount,
64
- results: results.map(result => ({
65
- test: result.test,
66
- content: result.content.replace(/\s+/g, ' ').trim(),
67
- dismissalKey: result.dismissalKey,
68
- html: result.element.outerHTML.slice(0, 500),
69
- xPath: window.getXPath(result.element)
70
- }))
71
- });
69
+ const standard = report.standard !== 'no';
70
+ // If standard results are to be reported:
71
+ if (standard) {
72
+ // Initialize the standard result.
73
+ result.standardResult = (0, standard_1.getStandardResult)();
74
+ }
75
+ // Get the tool script.
76
+ const script = await fs.readFile(`${__dirname}/../ed11y/editoria11y.min.js`, 'utf8');
77
+ // Perform the specified tests and populate the native result.
78
+ result.nativeResult = await page.evaluate(args => new Promise(async (resolve) => {
79
+ const { scriptNonce, script, rulesToTest } = args;
80
+ // When the script has been executed, creating data in an Ed11y object:
81
+ document.addEventListener('ed11yResults', async () => {
82
+ let { results } = Ed11y;
83
+ // If rules were selected:
84
+ if (rulesToTest) {
85
+ // Remove results of other rules.
86
+ results = results.filter(result => rulesToTest.includes(result.test));
87
+ }
88
+ // Return the native result.
89
+ resolve({
90
+ resultCount: results.length,
91
+ errorCount: Ed11y.errorCount,
92
+ warningCount: Ed11y.warningCount,
93
+ results: results.map(result => ({
94
+ test: result.test,
95
+ content: result.content.replace(/\s+/g, ' ').trim(),
96
+ dismissalKey: result.dismissalKey,
97
+ html: result.element.outerHTML.slice(0, 500),
98
+ xPath: window.getXPath(result.element)
99
+ }))
100
+ });
101
+ });
102
+ // Add the tool script to the page.
103
+ const toolScript = document.createElement('script');
104
+ if (scriptNonce) {
105
+ toolScript.nonce = scriptNonce;
106
+ console.log(`Added nonce ${scriptNonce} to tool script`);
107
+ }
108
+ toolScript.textContent = script;
109
+ document.body.insertAdjacentElement('beforeend', toolScript);
110
+ // Execute the tool script, creating Ed11y and triggering the event listener.
111
+ try {
112
+ await new Ed11y({
113
+ alertMode: 'headless'
114
+ });
115
+ }
116
+ catch (error) {
117
+ resolve({
118
+ prevented: true,
119
+ error: error.message
120
+ });
121
+ }
122
+ ;
123
+ }), {
124
+ scriptNonce,
125
+ script,
126
+ rulesToTest: act.rules
72
127
  });
73
- // Add the tool script to the page.
74
- const toolScript = document.createElement('script');
75
- if (scriptNonce) {
76
- toolScript.nonce = scriptNonce;
77
- console.log(`Added nonce ${scriptNonce} to tool script`);
128
+ // If the tool script failed to run:
129
+ if (result.nativeResult.prevented) {
130
+ // Report the prevention.
131
+ data.prevented = true;
132
+ data.error = result.nativeResult.error;
133
+ if (standard) {
134
+ result.standardResult.prevented = true;
135
+ }
78
136
  }
79
- toolScript.textContent = script;
80
- document.body.insertAdjacentElement('beforeend', toolScript);
81
- // Execute the tool script, creating Ed11y and triggering the event listener.
82
- try {
83
- await new Ed11y({
84
- alertMode: 'headless'
85
- });
137
+ // Otherwise, i.e. if it ran, and if a standard result is to be reported:
138
+ else if (standard) {
139
+ const { standardResult } = result;
140
+ const { warningCount, errorCount, results } = result.nativeResult;
141
+ // Populate the standard-result totals.
142
+ standardResult.totals = [warningCount, 0, errorCount, 0];
143
+ // For each native-result instance:
144
+ results.forEach(nativeInstance => {
145
+ // Create a standard-result instance.
146
+ const { test, content, dismissalKey, xPath } = nativeInstance;
147
+ // A dismissable warning is a manual check.
148
+ (0, standard_1.pushInstance)(standardResult, {
149
+ ruleID: test,
150
+ what: content,
151
+ ordinalSeverity: dismissalKey ? 0 : 2,
152
+ outcome: dismissalKey ? 'cantTell' : 'failed',
153
+ uncertainty: dismissalKey ? 'judgement-required' : undefined,
154
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
155
+ });
156
+ });
86
157
  }
87
- catch(error) {
88
- resolve({
89
- prevented: true,
90
- error: error.message
91
- });
158
+ return {
159
+ data,
160
+ result
92
161
  };
93
- }), {
94
- scriptNonce,
95
- script,
96
- rulesToTest: act.rules
97
- });
98
- // If a standard result is to be reported:
99
- if (standard) {
100
- const {standardResult} = result;
101
- const {warningCount, errorCount, results} = result.nativeResult;
102
- // Populate the standard-result totals.
103
- standardResult.totals = [warningCount, 0, errorCount, 0];
104
- // For each native-result instance:
105
- results.forEach(nativeInstance => {
106
- // Create a standard-result instance.
107
- const {test, content, dismissalKey, xPath} = nativeInstance;
108
- const instance = {};
109
- instance.ruleID = test;
110
- instance.what = content;
111
- instance.ordinalSeverity = dismissalKey ? 0 : 2;
112
- instance.count = 1;
113
- instance.catalogIndex = getXPathCatalogIndex(report, xPath);
114
- standardResult.instances.push(instance);
115
- });
116
- }
117
- return {
118
- data,
119
- result
120
- };
121
162
  };
163
+ exports.reporter = reporter;