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
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  © 2024–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jeff Witt.
3
4
  © 2025–2026 Jonathan Robert Pool.
4
5
 
5
6
  Licensed under the MIT License. See LICENSE file at the project root or
@@ -9,9 +10,10 @@
9
10
  */
10
11
 
11
12
  /*
12
- doActs
13
- Performs the tests of an act.
14
- This file is designed to be run as a child process.
13
+ doTestAct
14
+ Performs the tests of an act, under process isolation.
15
+ This file is designed to be run as a child process: it reads the temporary report, performs
16
+ the act with procs/testAct.js, writes the revised report, and messages the parent.
15
17
  */
16
18
 
17
19
  // ERROR LOGGING
@@ -32,31 +34,8 @@ process.on('unhandledRejection', reason => {
32
34
 
33
35
  // Module to perform file operations.
34
36
  const fs = require('fs/promises');
35
- // Module to close and launch browsers.
36
- const {browserClose, launch} = require('./launch');
37
-
38
- // CONSTANTS
39
-
40
- /*
41
- Tool XPath requirements.
42
- none: Needs no script or extra load time.
43
- own: Needs extra load time for its own XPath computations.
44
- script: Needs the window.getXPath script.
45
- attribute: Needs data-xpath attributes made with window.getXPath.
46
- */
47
- const xPathNeeds = {
48
- alfa: 'own',
49
- aslint: 'own',
50
- axe: 'attribute',
51
- ed11y: 'script',
52
- htmlcs: 'attribute',
53
- ibm: 'attribute',
54
- nuVal: 'attribute',
55
- nuVnu: 'attribute',
56
- qualWeb: 'attribute',
57
- wave: 'script'
58
- };
59
- const accessibleNameNeeders = ['testaro'];
37
+ // Function to perform a test act.
38
+ const {performTestAct} = require('./testAct');
60
39
 
61
40
  // FUNCTIONS
62
41
 
@@ -78,105 +57,32 @@ const doTestAct = async (reportPath, actIndex) => {
78
57
  // Get the temporary report.
79
58
  const reportJSON = await fs.readFile(reportPath, 'utf8');
80
59
  const report = JSON.parse(reportJSON);
81
- // Get a reference to the act in the report.
82
60
  const act = report.acts[actIndex];
83
- // Get the tool name.
84
- const {which} = act;
85
- let page;
86
- // If the tool is not Testaro:
87
- if (which !== 'testaro') {
88
- const browserID = act.launch && act.launch.browserID || report.browserID;
89
- const targetURL = act.launch && act.launch.target && act.launch.target.url || report.target.url;
90
- // Launch a browser, navigate to the URL, and update the run-module page export.
91
- page = await launch({
92
- report,
93
- actIndex,
94
- tempBrowserID: browserID,
95
- tempURL: targetURL,
96
- xPathNeed: xPathNeeds[which] ?? 'none',
97
- needsAccessibleName: accessibleNameNeeders.includes(which)
98
- });
99
- // If the launch aborted the job:
61
+ let status = 'ok';
62
+ let error = '';
63
+ try {
64
+ // Perform the act, revising the report.
65
+ await performTestAct({report, actIndex});
66
+ // If the launch aborted the job or no page existed, report that.
100
67
  if (report.jobData?.aborted) {
101
- // Close the browser and its context, if they exist.
102
- await browserClose(page);
103
- const reportJSON = JSON.stringify(report);
104
- // Save the revised report.
105
- await fs.writeFile(reportPath, reportJSON);
106
- // Report this.
107
- sendMessage({
108
- status: 'error',
109
- error: 'Page launch aborted'
110
- });
111
- process.exit(1);
68
+ status = 'error';
69
+ error = 'Page launch aborted';
112
70
  }
113
- }
114
- // If the page exists after the launch, or if the tool is Testaro:
115
- if (page || which === 'testaro') {
116
- try {
117
- // Make the act reporter perform the specified tests of the tool.
118
- const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
119
- // Add the data and result to the act.
120
- act.data = actReport.data;
121
- act.result = actReport.result;
122
- // If the tool reported that the page prevented testing:
123
- if (act.data && act.data.prevented) {
124
- const {standardResult} = act.result;
125
- // Add this to any standard result.
126
- if (standardResult) {
127
- standardResult.prevented = true;
128
- }
129
- // Add prevention data to the job data.
130
- report.jobData.preventions[which] = act.data.error;
131
- }
132
- // Close the browser and its context, if they exist.
133
- await browserClose(page);
134
- const reportJSON = JSON.stringify(report);
135
- // Save the revised report.
136
- await fs.writeFile(reportPath, reportJSON);
137
- // Send a completion message.
138
- sendMessage({
139
- status: 'ok',
140
- });
141
- process.exit(0);
71
+ else if (act.data?.prevented && act.data.error === 'No page') {
72
+ status = 'error';
73
+ error = 'ERROR: No page';
142
74
  }
143
- // If the tool invocation failed:
144
- catch(error) {
145
- // Close the browser and its context, if they exist.
146
- await browserClose(page);
147
- // Save the revised report.
148
- const reportJSON = JSON.stringify(report);
149
- await fs.writeFile(reportPath, reportJSON);
150
- // Report the failure.
151
- const message = error.message.slice(0, 400);
152
- console.log(`ERROR: Test act ${act.which} failed (${message})`);
153
- sendMessage({
154
- status: 'error',
155
- error: 'ERROR performing the act'
156
- });
157
- process.exit(1);
158
- };
159
75
  }
160
- // Otherwise, i.e. if the page does not exist after the launch:
161
- else {
162
- // Add this to the act.
163
- act.data ??= {};
164
- act.data.prevented = true;
165
- act.data.error = 'No page';
166
- // Add the prevention to the job data.
167
- report.jobData.preventions[which] = act.data.error;
168
- const reportJSON = JSON.stringify(report);
169
- // Save the revised report.
170
- await fs.writeFile(reportPath, reportJSON);
171
- // Report this.
172
- const message = 'ERROR: No page';
173
- console.log(message);
174
- sendMessage({
175
- status: 'error',
176
- error: message
177
- });
178
- process.exit(1);
76
+ // If the tool invocation failed:
77
+ catch(err) {
78
+ console.log(`ERROR: Test act ${act.which} failed (${err.message.slice(0, 400)})`);
79
+ status = 'error';
80
+ error = 'ERROR performing the act';
179
81
  }
82
+ // Save the revised report and report the outcome to the parent.
83
+ await fs.writeFile(reportPath, JSON.stringify(report));
84
+ sendMessage(status === 'ok' ? {status} : {status, error});
85
+ process.exit(status === 'ok' ? 0 : 1);
180
86
  };
181
87
  process.on('uncaughtException', error => {
182
88
  console.log(`ERROR: uncaughtException (${error.message})`);
package/procs/flow.js ADDED
@@ -0,0 +1,221 @@
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
+ /*
11
+ flow.js
12
+ Builds report.flow: the running list of issues across a job's checkpoints. For each pair of
13
+ consecutive checkpoints it records which issues were added, persisted, and removed by the
14
+ acts between them, together with a structure diff of the two catalogs and a line diff of
15
+ the two ARIA snapshots. See docs/checkpoint-scanning.md (Phase 2). Runs on the unpruned
16
+ catalog at job end, and can run on a stored report whose catalog was pruned (the structure
17
+ diff then covers cited elements only).
18
+ */
19
+
20
+ // IMPORTS
21
+
22
+ const {diffLines} = require('diff');
23
+ const {getStructureDiff} = require('./scope');
24
+
25
+ // CONSTANTS
26
+
27
+ // The most changed lines an ARIA snapshot diff records.
28
+ const maxAriaChanges = 500;
29
+
30
+ // FUNCTIONS
31
+
32
+ /*
33
+ Returns the identity of a standard instance for comparison across checkpoints:
34
+ tool, rule, element XPath, and start tag. The catalog index differs per checkpoint for the
35
+ same element, so the XPath stands for the element; the start tag distinguishes a replaced
36
+ element at the same path. Box and text are excluded because layout and copy shift without
37
+ the defect changing. A summary instance (no element) has an empty element key.
38
+ */
39
+ const getIdentity = (tool, instance, catalog) => {
40
+ const entry = instance.catalogIndex !== undefined && instance.catalogIndex !== ''
41
+ ? catalog[String(instance.catalogIndex)]
42
+ : null;
43
+ const pathID = entry ? entry.pathID ?? '' : instance.pathID ?? '';
44
+ const startTag = entry ? entry.startTag ?? '' : '';
45
+ return {
46
+ tool,
47
+ ruleID: instance.ruleID,
48
+ pathID,
49
+ startTag,
50
+ key: [tool, instance.ruleID, pathID, startTag].join('|')
51
+ };
52
+ };
53
+ // Returns whether an XPath is one of, or inside one of, the given root XPaths.
54
+ const isWithin = (pathID, roots) => roots.some(
55
+ root => pathID === root || pathID.startsWith(`${root}/`)
56
+ );
57
+ /*
58
+ Returns the issues found at a checkpoint, keyed by identity; the tools that observed it;
59
+ and, for each tool whose every act at the checkpoint was scoped to changed subtrees, the
60
+ XPaths of those subtrees (scopedRoots), since such a tool observed nothing outside them.
61
+ */
62
+ const getCheckpointIssues = (report, checkpointIndex) => {
63
+ const catalog = report.catalog ?? {};
64
+ const issues = {};
65
+ const tools = new Set();
66
+ const unscopedTools = new Set();
67
+ const scopedRoots = {};
68
+ report.acts.forEach((act, actIndex) => {
69
+ if (act.type !== 'test' || act.checkpoint !== checkpointIndex) {
70
+ return;
71
+ }
72
+ // A prevented act observed nothing.
73
+ if (act.data && act.data.prevented) {
74
+ return;
75
+ }
76
+ const standardResult = act.result && act.result.standardResult;
77
+ if (! standardResult || standardResult.prevented) {
78
+ return;
79
+ }
80
+ tools.add(act.which);
81
+ const scope = act.data && act.data.scope;
82
+ if (scope && scope.applied) {
83
+ scopedRoots[act.which] = [... (scopedRoots[act.which] ?? []), ... (scope.pathIDs ?? [])];
84
+ }
85
+ else {
86
+ unscopedTools.add(act.which);
87
+ }
88
+ (standardResult.instances ?? []).forEach(instance => {
89
+ const identity = getIdentity(act.which, instance, catalog);
90
+ const issue = issues[identity.key] ??= {
91
+ tool: identity.tool,
92
+ ruleID: identity.ruleID,
93
+ pathID: identity.pathID,
94
+ startTag: identity.startTag,
95
+ what: instance.what,
96
+ ordinalSeverity: instance.ordinalSeverity,
97
+ outcome: instance.outcome,
98
+ count: 0,
99
+ actIndexes: []
100
+ };
101
+ issue.count += instance.count || 1;
102
+ if (! issue.actIndexes.includes(actIndex)) {
103
+ issue.actIndexes.push(actIndex);
104
+ }
105
+ });
106
+ });
107
+ unscopedTools.forEach(tool => {
108
+ delete scopedRoots[tool];
109
+ });
110
+ return {issues, tools, scopedRoots};
111
+ };
112
+ // Returns a string ending with a newline, so the last line of a snapshot compares as a line.
113
+ const withFinalNewline = text => text && ! text.endsWith('\n') ? `${text}\n` : text;
114
+ // Returns a line diff of two ARIA snapshots: counts and the changed lines.
115
+ const getAriaDiff = (before, after) => {
116
+ const changes = [];
117
+ let addedLineCount = 0;
118
+ let removedLineCount = 0;
119
+ let truncated = false;
120
+ let line = 0;
121
+ diffLines(withFinalNewline(before ?? ''), withFinalNewline(after ?? '')).forEach(part => {
122
+ const lines = part.value.replace(/\n$/, '').split('\n');
123
+ if (part.added) {
124
+ addedLineCount += lines.length;
125
+ }
126
+ else if (part.removed) {
127
+ removedLineCount += lines.length;
128
+ }
129
+ if (part.added || part.removed) {
130
+ lines.forEach(text => {
131
+ if (changes.length < maxAriaChanges) {
132
+ changes.push({type: part.added ? 'added' : 'removed', line, text});
133
+ }
134
+ else {
135
+ truncated = true;
136
+ }
137
+ if (part.added) {
138
+ line++;
139
+ }
140
+ });
141
+ }
142
+ else {
143
+ line += lines.length;
144
+ }
145
+ });
146
+ return {addedLineCount, removedLineCount, truncated, changes};
147
+ };
148
+ /*
149
+ Builds and returns the flow of a report, or null if the report has fewer than two
150
+ checkpoints. For each pair of consecutive checkpoints, the issues of the tools that observed
151
+ both are compared: added (only at the later), persisted (at both), removed (only at the
152
+ earlier). Tools that observed only one of the two are listed as notObserved, since their
153
+ issues cannot be compared. An earlier issue that a tool did not re-test at the later
154
+ checkpoint, because its acts there were all scoped to changed subtrees and the issue's
155
+ element lies outside them, is listed as notRetested rather than removed.
156
+ */
157
+ exports.getFlow = report => {
158
+ const checkpoints = report.checkpoints ?? [];
159
+ if (checkpoints.length < 2) {
160
+ return null;
161
+ }
162
+ const observations = checkpoints.map(checkpoint => getCheckpointIssues(report, checkpoint.index));
163
+ const summaries = checkpoints.map((checkpoint, index) => {
164
+ const {issues, tools} = observations[index];
165
+ return {
166
+ index: checkpoint.index,
167
+ name: checkpoint.name,
168
+ kind: checkpoint.kind,
169
+ url: checkpoint.url,
170
+ actIndex: checkpoint.actIndex,
171
+ testActs: checkpoint.testActs,
172
+ tools: Array.from(tools).sort(),
173
+ issueCount: Object.keys(issues).length
174
+ };
175
+ });
176
+ const deltas = [];
177
+ for (let index = 1; index < checkpoints.length; index++) {
178
+ const before = observations[index - 1];
179
+ const after = observations[index];
180
+ const tools = Array.from(before.tools).filter(tool => after.tools.has(tool)).sort();
181
+ const notObserved = Array.from(new Set([... before.tools, ... after.tools]))
182
+ .filter(tool => ! tools.includes(tool))
183
+ .sort();
184
+ const comparable = issue => tools.includes(issue.tool);
185
+ const added = [];
186
+ const persisted = [];
187
+ const removed = [];
188
+ const notRetested = [];
189
+ Object.keys(after.issues).forEach(key => {
190
+ const issue = after.issues[key];
191
+ if (comparable(issue)) {
192
+ (before.issues[key] ? persisted : added).push(issue);
193
+ }
194
+ });
195
+ Object.keys(before.issues).forEach(key => {
196
+ const issue = before.issues[key];
197
+ if (comparable(issue) && ! after.issues[key]) {
198
+ const roots = after.scopedRoots[issue.tool];
199
+ const retested = ! roots || (issue.pathID && isWithin(issue.pathID, roots));
200
+ (retested ? removed : notRetested).push(issue);
201
+ }
202
+ });
203
+ // The structure diff recorded when the checkpoint was made, else one computed now (on a
204
+ // stored report, over the cited entries only).
205
+ const structure = checkpoints[index].structure
206
+ ?? getStructureDiff(report, checkpoints[index - 1].index, checkpoints[index].index);
207
+ deltas.push({
208
+ from: checkpoints[index - 1].index,
209
+ to: checkpoints[index].index,
210
+ tools,
211
+ notObserved,
212
+ added,
213
+ persisted,
214
+ removed,
215
+ notRetested,
216
+ structure,
217
+ aria: getAriaDiff(checkpoints[index - 1].ariaSnapshot, checkpoints[index].ariaSnapshot)
218
+ });
219
+ }
220
+ return {checkpoints: summaries, deltas};
221
+ };
@@ -0,0 +1,94 @@
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
+ /*
11
+ generateRuleRegistry
12
+ Build tool: writes testaro/registry.ts from the rule modules present in the
13
+ testaro directory (issue #73). Run with npm run build:registry; the CI
14
+ workflow regenerates the registry and fails on drift, so the committed
15
+ registry always matches the rule modules on disk.
16
+ */
17
+
18
+ // IMPORTS
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ // CONSTANTS
24
+
25
+ const ruleDir = path.join(__dirname, '..', 'testaro');
26
+ const registryPath = path.join(ruleDir, 'registry.ts');
27
+
28
+ // OPERATION
29
+
30
+ // Get the rule IDs: every TypeScript rule module in the directory.
31
+ const ruleIDs = fs.readdirSync(ruleDir)
32
+ .filter(name => name.endsWith('.ts') && ! name.endsWith('.d.ts'))
33
+ .map(name => name.replace(/\.ts$/, ''))
34
+ .filter(name => name !== 'registry')
35
+ .sort();
36
+ // Compose the registry source.
37
+ const lines = [];
38
+ lines.push(
39
+ '/*',
40
+ ' © 2026 Jeff Witt.',
41
+ '',
42
+ ' Licensed under the MIT License. See LICENSE file at the project root or',
43
+ ' https://opensource.org/license/mit/ for details.',
44
+ '',
45
+ ' SPDX-License-Identifier: MIT',
46
+ '*/',
47
+ '',
48
+ '/*',
49
+ ' registry',
50
+ ' GENERATED by procs/generateRuleRegistry.js (npm run build:registry); do not edit.',
51
+ ' Maps each rule ID to a lazy loader of its module, preserving on-demand loading',
52
+ ' while giving consumers a checked RuleID union and a typed reporter (issue #73).',
53
+ '*/',
54
+ '',
55
+ '// TYPES',
56
+ '',
57
+ '// The report every rule reporter resolves with.',
58
+ 'export interface RuleReport {',
59
+ ' data: unknown;',
60
+ ' totals: number[];',
61
+ ' standardInstances: unknown[];',
62
+ '}',
63
+ '// The module shape of a rule.',
64
+ 'export interface RuleModule {',
65
+ ' reporter: (...args: any[]) => Promise<RuleReport>;',
66
+ '}',
67
+ '',
68
+ '// The IDs of all rule modules.',
69
+ 'export type RuleID =');
70
+ ruleIDs.forEach((id, index) => {
71
+ lines.push(` | '${id}'${index === ruleIDs.length - 1 ? ';' : ''}`);
72
+ });
73
+ lines.push(
74
+ '',
75
+ '// CONSTANTS',
76
+ '',
77
+ '// Lazy loaders of the rule modules, keyed by rule ID.',
78
+ 'export const ruleModules = {');
79
+ ruleIDs.forEach(id => {
80
+ lines.push(` ${id}: () => require('./${id}') as typeof import('./${id}'),`);
81
+ });
82
+ lines.push(
83
+ '} satisfies Record<RuleID, () => RuleModule>;',
84
+ '');
85
+ // Write the registry if it changed.
86
+ const source = lines.join('\n');
87
+ const existing = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, 'utf8') : '';
88
+ if (source !== existing) {
89
+ fs.writeFileSync(registryPath, source);
90
+ console.log(`Wrote ${registryPath} with ${ruleIDs.length} rules`);
91
+ }
92
+ else {
93
+ console.log(`Registry is current (${ruleIDs.length} rules)`);
94
+ }
@@ -0,0 +1,26 @@
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
+ /*
11
+ getSource (hand-written declarations)
12
+ Minimal typings for the surface of procs/getSource.js that converted
13
+ TypeScript files use. Delete this file when getSource.js itself is converted
14
+ (issue #73).
15
+ */
16
+
17
+ import type {Page} from 'playwright';
18
+
19
+ export interface SourceData {
20
+ prevented: boolean;
21
+ source: string;
22
+ error?: string;
23
+ }
24
+
25
+ // Gets the source of the document of a page.
26
+ export function getSource(page: Page): Promise<SourceData>;
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jonathan Robert Pool.
3
4
 
4
5
  Licensed under the MIT License. See LICENSE file at the project root or
5
6
  https://opensource.org/license/mit/ for details.