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/README.md CHANGED
@@ -53,11 +53,13 @@ Testaro can perform tests of these _rule engines_:
53
53
  - [Editoria11y](https://github.com/itmaybejj/editoria11y) (Princeton University)
54
54
  - [HTML CodeSniffer](https://www.npmjs.com/package/html_codesniffer) (Squiz Labs)
55
55
  - [Nu Html Checker](https://github.com/validator/validator) (World Wide Web Consortium)
56
+ - [Pour Engine](https://github.com/pourdev/pour-engine) (David Yarham and Geoffrey Crofte)
56
57
  - [QualWeb](https://www.npmjs.com/package/@qualweb/core) (University of Lisbon)
58
+ - [SureA11y](https://github.com/SureA11y/core) (Jorge Rumoroso)
57
59
  - [Testaro](https://www.npmjs.com/package/testaro) (CVS Health)
58
60
  - [WAVE](https://wave.webaim.org/api/) (WebAIM)
59
61
 
60
- For the rule engines that are open-source, the identified organizations are their principal or original sponsors.
62
+ For the rule engines that are open-source, the identified organizations or persons are their principal or original sponsors or authors.
61
63
 
62
64
  As shown, Testaro is not only an integrator but also one of the integrated rule engines. That is because it provides about 50 tests of its own, mostly to complement tests provided by the other rule engines. Some of those Testaro tests are designed to act as approximate alternatives to tests of vulnerable, restricted, or no longer available rule engines. In all such cases the Testaro tests are independently designed and implemented, without reference to the code of the tests that inspired them.
63
65
 
@@ -81,7 +83,7 @@ The main concepts of Testaro are:
81
83
 
82
84
  ### Operating system and Node.js version
83
85
 
84
- Testaro can be installed under a MacOS, Windows, Debian, or Ubuntu operating system with the latest long-term-support version of [Node.js](https://nodejs.org/en/).
86
+ Testaro can be installed under a MacOS, Windows, Debian, or Ubuntu operating system with the latest long-term-support version of [Node.js](https://nodejs.org/en/). The minimum version is Node 22.12: Node 20 reached end of life in April 2026, and some dependencies (the Alfa packages and pixelmatch) are ES modules that Testaro loads with `require()`, which the Node 22 line supports from 22.12 onward. When Node 22 reaches end of life in April 2027, the minimum is expected to rise to Node 24.
85
87
 
86
88
  ### Browser security
87
89
 
@@ -115,7 +117,7 @@ This application implements option B.
115
117
 
116
118
  ## Installation as an independent application
117
119
 
118
- To install Testaro as an independent application, rather than a dependency, clone the [Testaro repository](https://github.com/jrpool/testaro). To ensure that the binary browsers of its Playwright dependency get installed, execute `(p)npx playwright install` after executing `(p)npm install`.
120
+ To install Testaro as an independent application, rather than a dependency, clone the [Testaro repository](https://github.com/YRA-Tech/testaro). To ensure that the binary browsers of its Playwright dependency get installed, execute `(p)npx playwright install` after executing `(p)npm install`.
119
121
 
120
122
  To update Testaro when it is an independent application, execute:
121
123
 
@@ -129,6 +131,8 @@ git pull
129
131
 
130
132
  The `.env` file stores your decisions about the environment in which Testaro runs. The variables that can be defined there are documented in the `env.example` file.
131
133
 
134
+ Deployment options let an operator set a fleet-wide policy that a job can override: the load state and time limit a navigation waits for and whether a 4xx response ends launch retries at once (`NAV_WAIT_UNTIL`, `NAV_TIMEOUT`, `NAV_FAIL_FAST_4XX`, or a job's `navigation` property); a scanner identity sent as the `X-YRA-Scanner` request header (`SCANNER_ID` or a job's `scannerId`); a full-page scroll after navigation so lazily loaded content is present (`PRESCAN_SCROLL` or a job's `scroll`); a branded Chromium channel (a job's `browserChannel`); launch retries per rule of the `testaro` tool (`TESTARO_RULE_RETRIES` or a `testaro` test act's `retries`); and whether the `qualWeb` tool's browser runs stealth evasions and blocks ads and trackers (`QUALWEB_STEALTH`, `QUALWEB_ADBLOCK`, or a `qualWeb` test act's `stealth` and `adBlock`). The defaults keep the behavior of earlier versions.
135
+
132
136
  ## Jobs
133
137
 
134
138
  Jobs tell Testaro what to do.
@@ -164,6 +168,20 @@ Here is a sample job, showing properties that you can set:
164
168
  // Chromium-specific). Defaults to true. Set false to opt
165
169
  // out — useful for sites whose anti-bot heuristics react
166
170
  // badly to stealth's patches.
171
+ browserChannel: 'chrome', // Optional. Run an installed branded Chromium (chrome or msedge)
172
+ // instead of the bundled build; bundled or absent keeps the default.
173
+ scannerId: 'MyScanner/1.0; +https://example.com/scanner', // Optional. Sent as the
174
+ // X-YRA-Scanner request header (default SCANNER_ID, else no header).
175
+ scroll: false, // Optional. Scroll the full page after navigation so lazily loaded content
176
+ // is present before tools run (default PRESCAN_SCROLL, else false).
177
+ navigation: { // Optional. Load state to wait for (networkidle, load, or domcontentloaded),
178
+ // time limit in ms, and whether a 4xx response ends launch retries at once
179
+ // (defaults NAV_WAIT_UNTIL, NAV_TIMEOUT, NAV_FAIL_FAST_4XX, else networkidle,
180
+ // 10000, false).
181
+ waitUntil: 'load',
182
+ timeout: 30000,
183
+ failFast4xx: true
184
+ },
167
185
  creationTimeStamp: '241229T0537', // When job was created
168
186
  executionTimeStamp: '250110T1200', // When job will be ready to be performed
169
187
  target: {
@@ -288,32 +306,34 @@ In both cases, the first argument of `dirWatch` tells Testaro whether to continu
288
306
 
289
307
  ### Server polling
290
308
 
291
- Testaro can poll a server for jobs to be performed. The server can act as the “controller” described in [How to run a thousand accessibility tests](https://medium.com/cvs-health-tech-blog/how-to-run-a-thousand-accessibility-tests-63692ad120c3). The server is responsible for preparing Testaro jobs, assigning them to Testaro agents, receiving reports back from those agents, and performing any further processing of the reports, including enhancement, storage, and disclosure to audiences. It can be any server reachable with a URL. That includes a server running on the same host as Testaro, with a URL such as `localhost:3000`.
309
+ Testaro can poll a server for jobs to be performed. The server can act as the “controller” described in [How to run a thousand accessibility tests](https://medium.com/cvs-health-tech-blog/how-to-run-a-thousand-accessibility-tests-63692ad120c3). The server is responsible for preparing Testaro jobs, assigning them to Testaro workers, receiving reports back from those workers, and performing any further processing of the reports, including enhancement, storage, and disclosure to audiences. It can be any server reachable with a URL. That includes a server running on the same host as Testaro, with a URL such as `localhost:3000`.
310
+
311
+ To allow Testaro to poll a server for jobs, define the environment variables documented under `netWatch variables` in the [env.example](env.example) file. The URL paths are determined by agreement between Testaro and the server. A single Testaro instance can watch one server.
292
312
 
293
- To allow Testaro to poll a server for jobs, define the following environment variables:
313
+ `NETWATCH_AUTH_TYPE` selects how Testaro authenticates to the server:
294
314
 
295
- - `NETWATCH_URL_JOB`: which URL to poll (the URL must contain the value of the `AGENT` environment variable)
296
- - `NETWATCH_URL_REPORT`: which URL to send job reports to
297
- - `NETWATCH_URL_AUTH`: the password to supply to the server when polling and when delivering a report
315
+ - `none`: no credentials are sent. `NETWATCH_WORKER_ID` and `NETWATCH_WORKER_SECRET` are not required.
316
+ - `pathBody`: the password (`NETWATCH_WORKER_SECRET`) is transmitted in the request body as the value of an `agentPW` property. If the server requires the ID of the Testaro instance in the URL path, include it in `NETWATCH_URL_JOB` and `NETWATCH_URL_REPORT` yourself; Testaro does not insert it.
317
+ - `header`: the request carries an `authorization` header whose value is `Basic`, followed by a space and the base64 encoding of `NETWATCH_WORKER_ID:NETWATCH_WORKER_SECRET`. The worker ID must not contain a colon.
298
318
 
299
- The job request sent to the server can be a `POST` request, in which the `agentPW` property of the payload will be the password. Or it can be a `GET` request with the URL containing the password.
319
+ Testaro sends job requests and completed reports as `POST` requests. When Testaro sends a report to the server, the report is the value of a `report` property in the request body. If `NETWATCH_WORKER_ID` is defined, Testaro also records it as the `sources.agent` property of the report, so the server can attribute the report to this instance under any auth type.
300
320
 
301
- Testaro will send the report as a `POST` request whose payload is a JSON object with two properties: `agentPW` (the password) and `report` (the report). However, if the environment does not contain a password, the payload is a JSON object containing only the report.
321
+ The `AGENT` and `NETWATCH_URL_AUTH` variables of earlier versions are deprecated. Testaro still honors them (as `NETWATCH_WORKER_ID` and as a `pathBody` password, respectively) but warns; rename them.
302
322
 
303
323
  An application can make Testaro poll a server for jobs with:
304
324
 
305
325
  ```javaScript
306
326
  const {netWatch} = require('testaro/netWatch');
307
- netWatch(true, 300, true);
327
+ netWatch(true, 300);
308
328
  ```
309
329
 
310
330
  A user can make Testaro poll a server for jobs with:
311
331
 
312
332
  ```bash
313
- node call netWatch true 300 true
333
+ node call netWatch true 300
314
334
  ```
315
335
 
316
- The first argument of `netWatch` tells Testaro whether to continue polling after performing the first job. The second argument tells Testaro how many seconds to wait after receiving a no-jobs response before polling again. The third argument tells Testaro whether to be certificate-tolerant, i.e. to accept SSL certificates that fail verification against a list of certificate authorities (the default is `true`).
336
+ The first argument of `netWatch` tells Testaro whether to continue polling after performing the first job. The second argument tells Testaro how many seconds to wait after receiving a no-jobs response before polling again. The optional third argument tells Testaro whether to be certificate-tolerant, i.e. to accept SSL certificates that fail verification against a list of certificate authorities (the default is `false`). Certificate tolerance disables the protection of an `https` connection, exposing credentials and reports to interception, so use it only against servers you control, such as local test servers with self-signed certificates.
317
337
 
318
338
  ## Reports
319
339
 
@@ -361,6 +381,7 @@ Testaro uses the following techniques to make the rule engines calculate XPaths:
361
381
 
362
382
  - `alfa` and `aslint`: They report XPaths, so Testaro needs only to normalize them.
363
383
  - `ed11y`: Testaro adds it and a `window.getXPath` method to the page. When the rule engine reports an element, Testaro computes its XPath.
384
+ - `pour` and `surea11y`: Testaro adds the vendored engine bundle and a `window.getXPath` method to the page. Each engine reports a CSS selector for each element; Testaro finds the element in the page via its selector and executes `window.getXPath` on it.
364
385
  - `wave`: It reports a selector for each element; Testaro finds each element in the page via its selector and executes `window.getXPath` on the element.
365
386
  - `htmlcs`, `ibm`, `nuVal`, `nuVnu`, `qualWeb`: Testaro adds `data-xpath` attributes to all elements. The rule engines include code excerpts, with the `data-xpath` attributes, in the reported violations.
366
387
  - `axe`: It reports a selector for each element, and Testaro adds `data-xpath` attributes to all elements. Testaro finds each element in the page via its selector and uses the `data-xpath` attribute. When this fails, Testaro uses the `data-xpath` attribute if its complete value is included in the reported `node.html` value.
@@ -405,24 +426,49 @@ Details about these expectation properties are documened in the `VALIDATION.md`
405
426
 
406
427
  #### Standard results
407
428
 
408
- If the job instructs Testaro to include standard results, then the `result.standardResult` property of each act of type `test` will have three properties:
429
+ If the job instructs Testaro to include standard results, then the `result.standardResult` property of each act of type `test` will have four properties:
409
430
 
410
431
  - `prevented`: Whether the rule engine was prevented from performing the act
411
432
  - `totals`: An array of 4 integers, counting the rule violations at 4 severity levels
433
+ - `outcomeTotals`: An object counting the rule violations by outcome: `{failed, cantTell}`
412
434
  - `instances`: An array of data about the violations reported by the rule engine
413
435
 
414
436
  More specifically:
415
437
 
416
438
  - The `totals` value is an array like this: `[3, 0, 87, 4]`. This example would mean that the rule engine reported 3 failures at severity 0 (the least severe level), none at severity 1, 87 at severity 2, and 4 at severity 3. These four severities are conceptually ordinal, not metric.
439
+ - The `outcomeTotals` value is an object like this: `{failed: 87, cantTell: 7}`. It counts the violations (weighted by `count`) that the rule engine asserted versus those it flagged as uncertain.
417
440
  - The `instances` value is an array of objects, each having these properties:
418
- - `ruleId`: The ID of the rule that was violated
441
+ - `ruleID`: The ID of the rule that was violated
419
442
  - `what`: A description of the rule or of the violation
420
443
  - `ordinalSeverity`: The severity of the violation
444
+ - `outcome`: `failed` if the rule engine asserted the violation, or `cantTell` if the rule engine reported that it could not determine whether the rule was violated (the vocabulary of the ACT Rules Format). Every instance has an outcome.
445
+ - `uncertainty` (only with `cantTell`, and only if the rule engine gave a reason): One of `not-computable`, `judgement-required`, `runtime-dependent`, `spec-only`, `equivalence-unknown`, `out-of-scope`
446
+ - `needed` (only with `cantTell`, and only if the rule engine said): What a reviewer must determine to resolve the uncertainty
421
447
  - `count`: How many violations of the rule this instance reports
422
448
  - `catalogIndex`: Key of the HTML element in the catalog
423
449
 
424
450
  If no catalog entry was found for the instance, then instead of a `catalogIndex` property Testaro tries to insert a `pathID` property, whose value is a normalized XPath of the offending HTML element.
425
451
 
452
+ The `outcome` property is the authoritative certainty signal. In version 78, each tool's `ordinalSeverity` conventions are unchanged from earlier versions: most tools encode uncertainty as a low severity (for example, axe `incomplete` results have severities 0 and 1, and `violations` have severities 2 and 3), so `ordinalSeverity` still mixes certainty with impact, and it mixes them differently per tool. Consumers should read `outcome` for certainty and should not infer it from `ordinalSeverity`. A later major version will redefine `ordinalSeverity` as impact only (0 minor, 1 moderate, 2 serious, 3 critical). Design record: `docs/standard-result-outcome.md`.
453
+
454
+ Testaro's own rules report `failed` unless the rule's entry in `allRules` (in `tests/testaro.ts`) specifies `outcome: 'cantTell'` (for example, `allCaps`, whose violations are AI estimates) or a violation description carries a prefix: `2:` sets severity 2, `2?:` sets severity 2 and outcome `cantTell`, and `?:` sets outcome `cantTell` at the rule's default severity.
455
+
456
+ ### Checkpoints
457
+
458
+ A job's acts run in order: a `launch` act opens a page, interaction acts (`button`, `link`, `text`, `press`, `url`, and so on) act on it, and `test` acts run rule engines. A **checkpoint** is a named page state reached by that flow, snapshotted and tested. Checkpoint 0 is the job target as launched; a `checkpoint` act (`{type: 'checkpoint', which: 'name'}`) creates the next one from the live page. A `test` act tests the most recent checkpoint, and each of its standard instances carries the `checkpoint` index. `report.checkpoints[k]` describes each checkpoint: its name, URL, title, the page images (`imageIndexes` into `report.images`), the catalog entries it added (`catalogRange`; every catalog entry also carries `checkpoint`), an ARIA snapshot, and, for a state reached by interaction, the acts (`replay`) that a test act's browser re-enacts after navigating to `launchURL` before the rule engine runs. Each such test act records `data.replay` with the count of replayed acts and a `fidelity` of `exact` or `divergent`, comparing the replayed page's DOM with the snapshot.
459
+
460
+ A job's `isolation` property (or the `ISOLATION` environment default) sets how test acts are isolated: `process` (the default, and the previous behavior) runs each test act in a child process with its own browser; `browser` runs test acts in the job's process, each in a fresh context of one browser shared by the job; `page` runs them in the job's process on the live page of the current checkpoint, with no replay, so tools may contaminate one another's page (contaminating testaro rules still get a fresh page). `jobData.isolation` records the level used. Only `process` isolation kills a tool that overruns its time limit; the others report the act as timed out and continue.
461
+
462
+ When a job has two or more checkpoints, the report gains `flow`, the running list of issues across them. `flow.checkpoints[k]` summarizes each checkpoint (the tools that observed it and its issue count), and `flow.deltas[k - 1]` compares checkpoints `k - 1` and `k`: the issues `added` by the acts between them, those that `persisted`, and those `removed`, each identified by tool, rule ID, element XPath (`pathID`), and start tag, so the same element in two page states is one issue. Only tools that observed both checkpoints are compared (`tools`); the rest are listed as `notObserved`. An earlier issue that a tool did not re-test at the later checkpoint, because its acts there were all scoped to changed subtrees that exclude the issue's element, is listed as `notRetested`, not `removed`. Each delta also carries `structure`, the difference between the two checkpoints' catalogs as XPaths (`added`, `removed`, `changed` start tags, `textChanged`, and the outermost changed elements as `roots`), and `aria`, a line diff of the two ARIA snapshots.
463
+
464
+ Interaction acts can identify their elements by a `selector` property (any Playwright selector, such as `#name`, `text="Continue"`, or `role=button[name="Continue"]`) instead of an element type and text substring; a `text` act with `clear: true` replaces the input's value. With these, a recorded user path (a start URL and actions of the kinds a Playwright recording yields: `click`, `fill`, `select`, `navigate`, `wait`, `checkpoint`) converts directly into a job: `require('testaro/procs/userPath').getUserPathActs({startUrl, actions, testActs})` returns the acts, one per action, with each `checkpoint` action becoming a `checkpoint` act followed by a copy of each test act in `testActs`.
465
+
466
+ When a checkpoint is created, the catalog entries of the previous checkpoint that none of its test acts cited are pruned at once, since no later test act can cite them, so a long flow does not accumulate every page state's catalog in the report that each test act reads.
467
+
468
+ A `test` act may have a `scope` property. The default, `page`, tests the whole page of the act's checkpoint. With `changed`, the act tests only the subtrees that changed since the previous checkpoint (the `roots` of the structure diff, as CSS selectors), for the rules and tools that can be so restricted: the `testaro` rules declared element-local (`local: true` in `allRules`), `axe` (by its `include` context), and `surea11y` (by its context selector, the nearest common ancestor of the roots). Page-level `testaro` rules (heading order, landmarks, duplicate IDs, focus order, hover, motion, and others) and all other tools test the whole page whatever the scope, because any change can alter their verdicts. The act records `data.scope` (`requested`, `applied`, `reason`, `roots`, `pathIDs`, and for `testaro` the `localRules` and `pageRules`), so a reader can see what was and was not re-tested. The act is not scoped, and `jobData.warnings` says why, when there is no previous checkpoint, nothing changed, more than 50 subtrees changed, or the tool cannot restrict itself.
469
+
470
+ Checkpoint acts are optional. A job without one behaves as before: every test act tests checkpoint 0, and interaction acts before a test act only produce a warning in `jobData.warnings`. In a job with checkpoint acts, interaction acts followed by a test act without a checkpoint act produce an implicit checkpoint and a warning. A rule engine that tests a URL rather than a page (WAVE, and the Nu checkers with `withSource`) cannot test a state reached by interaction; such a test act is prevented with the reason. Details: `docs/checkpoint-scanning.md`.
471
+
426
472
  ## Rule-engine details
427
473
 
428
474
  The rule engines whose tests Testaro performs have particularities described below.
@@ -494,6 +540,10 @@ The `nuVal` and `nuVnu` rule engines perform the tests of the Nu Html Checker. T
494
540
 
495
541
  Its `rules` argument is **not** an array of rule IDs, but instead is an array of rule _specifications_. A rule specification for `nuVal` or `nuVnu` is a string with the format `=ruleID` or `~ruleID`. The `=` prefix indicates that the rule ID is invariable. The `~` prefix indicates that the rule ID is variable, in which case the `ruleID` part of the specification is a matching regular expression, rather than the exact text of a message. This `rules` format arises from the fact that `nuVal` and `nuVnu` generate customized messages and do not accompany them with rule identifiers.
496
542
 
543
+ ### Pour Engine
544
+
545
+ The `pour` rule engine makes use of the `pour/pour.min.js` file, a bundle that Testaro builds from the upstream [pour-engine](https://github.com/pourdev/pour-engine) repository (MIT), because upstream publishes no distributable bundle. The `pour/README.md` file documents the build command and the pinned upstream version. Testaro injects the bundle into the page and runs every rule. Findings in the engine's `violations` bucket become standard instances with outcome `failed`; findings in its `incomplete` bucket become instances with outcome `cantTell`. The engine's `passes`, `inapplicable`, and `manualReview` buckets are tallied in the act's `data` property and never become instances. If the bundle is missing or fails to define its global, the act is reported as prevented.
546
+
497
547
  ### QualWeb
498
548
 
499
549
  The `qualWeb` rule engine performs the ACT rules, WCAG Techniques, and best-practices tests of QualWeb. Only failures and warnings are included in the report. The EARL report of QualWeb is not generated, because it is equivalent to the report of the ACT rules tests.
@@ -519,13 +569,17 @@ Thus, when the `rules` argument is omitted, QualWeb will test for all of the rul
519
569
 
520
570
  The target can be provided to QualWeb either as HTML or as a URL. Experience indicates that the results can differ between these methods, with each method reporting some rule violations or some instances that the other method does not report. For at least some cases, more rules are reported violated when HTML is provided (`withNewItems: false`).
521
571
 
522
- QualWeb creates sandboxed Puppeteer pages to perform its tests on. Therefore, the host must permit sandboxed browsers to be launched. See the discussion above about browser security.
572
+ QualWeb creates sandboxed Playwright pages to perform its tests on. Therefore, the host must permit sandboxed browsers to be launched. See the discussion above about browser security.
573
+
574
+ ### SureA11y
575
+
576
+ The `surea11y` rule engine makes use of the `surea11y/surea11y.browser.js` file, the standalone browser bundle that [`@surea11y/core`](https://github.com/SureA11y/core) publishes, vendored verbatim. That package is licensed under the Mozilla Public License 2.0, unlike the MIT-licensed or Apache-licensed rule engines; the vendored file must not be modified. The `surea11y/README.md` file documents the pinned upstream version and the result shape. Testaro injects the bundle into the page and runs every automatic rule. The engine reports an outcome per rule (`pass`, `fail`, `cantTell`, or `notApplicable`) and may grade individual occurrences of a `fail` rule as `cantTell`. Occurrences graded `fail` become standard instances with outcome `failed`; occurrences graded `cantTell` become instances with outcome `cantTell`, carrying the engine's `uncertainty` code and `needed` guidance when it supplies them. Rules of type `manual` and rules with outcome `pass` or `notApplicable` are tallied in the act's `data` property and never become instances.
523
577
 
524
578
  ### Testaro
525
579
 
526
580
  The rules that Testaro can test for are implemented in files within the `testaro` directory.
527
581
 
528
- The Testaro rules are classified by an `allRules` array defined in the `tests/testaro.js` file. Each item in that array is an object with these properties:
582
+ The Testaro rules are classified by an `allRules` array defined in the `tests/testaro.ts` file. Each item in that array is an object with these properties:
529
583
 
530
584
  - `id`: the rule ID.
531
585
  - `what`: a description of the rule.
@@ -558,7 +612,7 @@ If you want the stand-alone API to perform the tests, you need to have that API
558
612
 
559
613
  You can define additional Testaro rules and functionality. Contributions are welcome.
560
614
 
561
- Please report any issues, including feature requests, at the [repository](https://github.com/jrpool/testaro/issues).
615
+ Please report any issues, including feature requests, at the [repository](https://github.com/YRA-Tech/testaro/issues).
562
616
 
563
617
  ## Accessibility principles
564
618
 
@@ -654,7 +708,7 @@ From 12 February 2024 through 30 September 2025, contributors of code to Testaro
654
708
 
655
709
  ## Future work
656
710
 
657
- Future work contemplated for this project is described in its [issues](https://github.com/jrpool/testaro/issues) and also discussed in the [UPGRADES.md](UPGRADES.md) file.
711
+ Future work contemplated for this project is described in its [issues](https://github.com/YRA-Tech/testaro/issues) and also discussed in the [UPGRADES.md](UPGRADES.md) file.
658
712
 
659
713
  ## Etymology
660
714
 
package/UPGRADES.md CHANGED
@@ -8720,3 +8720,7 @@ you'll get notified when `#334` merges and when the subsequent changesets "Versi
8720
8720
  - **Re-test**: run a job with a `qualWeb` act and confirm the `No DOM` error is gone before merging your `qwpw` branch.
8721
8721
 
8722
8722
  I'll be ready to help with the dependency bump and verification when that release lands.
8723
+
8724
+ # Certainty in the standard result (2026-09-01)
8725
+
8726
+ Decision: add a first-class `outcome` (`failed` | `cantTell`) to every standard instance, with optional `uncertainty` (six ACT-style codes borrowed from surea11y) and `needed` (reviewer guidance), plus `standardResult.outcomeTotals`. `ordinalSeverity` and `totals` are unchanged in 78.x; a later major version redefines `ordinalSeverity` as impact only. This follows the `allCaps` ruling above (do not overload `ordinalSeverity` with confidence) by giving certainty its own field rather than by hiding it in `what`. Shared shape helpers live in `procs/standard.js`; the alfa adapter no longer rewrites rule IDs to `cantTell` / `cantTellTextContrast`. Full design, including the recommended Testilo tic consolidation of the 57 `*Risk` issues and certainty-aware scoring: `docs/standard-result-outcome.md`.
package/actSpecs-doc.md CHANGED
@@ -55,9 +55,13 @@ The validity criterion named in item 2 may be any of these:
55
55
  - `'isWaitable'`: is `'url'`, `'title'`, or `'body'`
56
56
  - `'areStrings'`: is an array of strings
57
57
 
58
+ ## qualWeb tool
59
+
60
+ A `qualWeb` test act may have `stealth` and `adBlock` boolean properties, which set whether the tool's browser runs stealth evasions and blocks ads and trackers (defaults `QUALWEB_STEALTH` and `QUALWEB_ADBLOCK`, else true).
61
+
58
62
  ## testaro tool
59
63
 
60
- The `tools.testaro` object has an `args` property specifying that a `testaro` test act may include an `args` property with an object value.
64
+ The `tools.testaro` object has a `retries` property (launch retries per rule; default `TESTARO_RULE_RETRIES`, else 2) and an `args` property specifying that a `testaro` test act may include an `args` property with an object value.
61
65
 
62
66
  If it does, the property names of the object value must be `testaro` rule IDs. Any property value must be an array of the positional arguments to be concatenated to the four default arguments (`page`, `report`, `actIndex`, and `withItems`) in the signature of the `reporter` function of each `testaro` rule.
63
67
 
@@ -84,3 +88,11 @@ Licensed under the [MIT License](https://opensource.org/license/mit/). See [LICE
84
88
  at the project root for details.
85
89
 
86
90
  SPDX-License-Identifier: MIT
91
+
92
+ ## Checkpoint acts
93
+
94
+ The move acts (`button`, `checkbox`, `link`, `radio`, `search`, `select`, `text`) may have a `selector` property, any Playwright selector, which replaces the act type's element selector and the text matching of `which`; `index` still selects among the matches. A `checkbox` or `radio` act must have `which` or `selector`. A `text` act with `clear: true` replaces the input's value instead of appending to it. A `select` act whose `what` matches no option text falls back to Playwright's matching of `what` as an option value or label. `procs/userPath.js` builds acts of these kinds from a recorded user path.
95
+
96
+ A `test` act may have a `scope` property: `page` (the default) tests the whole page of the act's checkpoint; `changed` tests only the subtrees that changed since the previous checkpoint, for the rules and tools that can be so restricted (the element-local `testaro` rules, `axe`, and `surea11y`), and the whole page for the rest. A job with a changed-scope test act must have a checkpoint act. The act records what it was given as `data.scope`. See `docs/checkpoint-scanning.md`.
97
+
98
+ A `checkpoint` act snapshots the current page state as a checkpoint that subsequent `test` acts observe. Its `which` property names the checkpoint (unique within the job, and not `start`, the name of checkpoint 0). A checkpoint act must follow a `launch` act. See `docs/checkpoint-scanning.md`.
package/actSpecs.js CHANGED
@@ -19,15 +19,24 @@ exports.actSpecs = {
19
19
  button: [
20
20
  'Click a button or submit input',
21
21
  {
22
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
22
23
  which: [false, 'string', 'hasLength', 'substring of button text'],
23
24
  index: [false, 'number', '', 'index among matches if not 0'],
24
25
  what: [false, 'string', 'hasLength', 'comment']
25
26
  }
26
27
  ],
28
+ checkpoint: [
29
+ 'Snapshot the current page state (catalog, page image, ARIA snapshot) as a checkpoint that subsequent test acts observe',
30
+ {
31
+ which: [true, 'string', 'hasLength', 'checkpoint name, unique within the job'],
32
+ what: [false, 'string', 'hasLength', 'comment']
33
+ }
34
+ ],
27
35
  checkbox: [
28
36
  'Check a checkbox',
29
37
  {
30
- which: [true, 'string', 'hasLength', 'substring of checkbox text'],
38
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
39
+ which: [false, 'string', 'hasLength', 'substring of checkbox text (required unless selector is given)'],
31
40
  index: [false, 'number', '', 'index among matches if not 0'],
32
41
  what: [false, 'string', 'hasLength', 'comment']
33
42
  }
@@ -52,6 +61,7 @@ exports.actSpecs = {
52
61
  link: [
53
62
  'Click a link and wait for the page to be idle or loaded',
54
63
  {
64
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
55
65
  which: [false, 'string', 'hasLength', 'substring of link text'],
56
66
  index: [false, 'number', '', 'index among matches if not 0'],
57
67
  what: [false, 'string', 'hasLength', 'comment']
@@ -96,7 +106,8 @@ exports.actSpecs = {
96
106
  radio: [
97
107
  'Check a radio button',
98
108
  {
99
- which: [true, 'string', 'hasLength', 'substring of radio-button text'],
109
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
110
+ which: [false, 'string', 'hasLength', 'substring of radio-button text (required unless selector is given)'],
100
111
  index: [false, 'number', '', 'index among matches if not 0'],
101
112
  what: [false, 'string', 'hasLength', 'comment']
102
113
  }
@@ -110,6 +121,7 @@ exports.actSpecs = {
110
121
  search: [
111
122
  'Enter text into a search input, optionally with 1 placeholder for an all-caps literal environment variable',
112
123
  {
124
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
113
125
  which: [false, 'string', 'hasLength', 'substring of input text'],
114
126
  index: [false, 'number', '', 'index among matches if not 0'],
115
127
  what: [true, 'string', 'hasLength', 'text to enter, with optional __PLACEHOLDER__']
@@ -118,6 +130,7 @@ exports.actSpecs = {
118
130
  select: [
119
131
  'Select a select option',
120
132
  {
133
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
121
134
  which: [false, 'string', 'hasLength', 'substring of select-list text'],
122
135
  index: [false, 'number', '', 'index among matches if not 0'],
123
136
  what: [true, 'string', 'hasLength', 'substring of option text content']
@@ -144,15 +157,18 @@ exports.actSpecs = {
144
157
  {
145
158
  which: [true, 'string', 'isTest', 'tool name'],
146
159
  launch: [false, 'object', '', 'new target, browserID, and/or what, if any'],
147
- rules: [false, 'array', 'areStrings', 'rule IDs or (for testaro, nuVal, or nuVnu) specifications, if not all']
160
+ rules: [false, 'array', 'areStrings', 'rule IDs or (for testaro, nuVal, or nuVnu) specifications, if not all'],
161
+ scope: [false, 'string', 'isScope', 'page (default) or changed (only the subtrees changed since the previous checkpoint, for rules and tools that can be so restricted)']
148
162
  }
149
163
  ],
150
164
  text: [
151
165
  'Enter text into a text input, optionally with 1 placeholder for an all-caps literal environment variable',
152
166
  {
167
+ selector: [false, 'string', 'hasLength', 'Playwright selector of the element, instead of an element type and text substring'],
153
168
  which: [false, 'string', 'hasLength', 'substring of input text'],
154
169
  index: [false, 'number', '', 'index among matches if not 0'],
155
- what: [true, 'string', 'hasLength', 'text to enter, with optional __PLACEHOLDER__']
170
+ what: [true, 'string', 'hasLength', 'text to enter, with optional __PLACEHOLDER__'],
171
+ clear: [false, 'boolean', '', 'whether to replace any existing value instead of appending']
156
172
  }
157
173
  ],
158
174
  url: [
@@ -196,12 +212,20 @@ exports.actSpecs = {
196
212
  withSource: [false, 'boolean', '', 'whether to use the page source and not the browser page']
197
213
  }
198
214
  ],
215
+ qualWeb: [
216
+ 'Perform QualWeb tests',
217
+ {
218
+ stealth: [false, 'boolean', '', 'whether the browser runs stealth evasions (default QUALWEB_STEALTH, else true)'],
219
+ adBlock: [false, 'boolean', '', 'whether the browser blocks ads and trackers (default QUALWEB_ADBLOCK, else true)']
220
+ }
221
+ ],
199
222
  testaro: [
200
223
  'Perform Testaro tests',
201
224
  {
202
225
  withItems: [true, 'boolean', '', 'itemize'],
203
226
  stopOnFail: [true, 'boolean', '', 'whether testing is to stop after first failure'],
204
- args: [false, 'object', 'areArrays', 'extra arguments of rules taking any']
227
+ args: [false, 'object', 'areArrays', 'extra arguments of rules taking any'],
228
+ retries: [false, 'number', '', 'launch retries per rule (default TESTARO_RULE_RETRIES, else 2)']
205
229
  }
206
230
  ],
207
231
  wave: [
package/call.js CHANGED
@@ -45,8 +45,8 @@ const callRun = async jobIDStart => {
45
45
  const jobDirFileNames = await fs.readdir(todoDir);
46
46
  const jobFileNames = jobDirFileNames.filter(fileName => fileName.endsWith('.json'));
47
47
  const specifiedJobFileNames = jobIDStart
48
- ? jobFileNames.filter(fileName => fileName.startsWith(jobIDStart))
49
- : jobFileNames;
48
+ ? jobFileNames.filter(fileName => fileName.startsWith(jobIDStart))
49
+ : jobFileNames;
50
50
  const jobFileName = specifiedJobFileNames[0];
51
51
  // If it exists:
52
52
  if (jobFileName) {
@@ -74,7 +74,7 @@ const callDirWatch = async (isForever, intervalInSeconds) => {
74
74
  };
75
75
  // Starts a network watch, converting the interval argument to a number.
76
76
  const callNetWatch = async (isForever, intervalInSeconds, isCertTolerant) => {
77
- await netWatch(
77
+ return await netWatch(
78
78
  isForever === 'true',
79
79
  Number.parseInt(intervalInSeconds, 10),
80
80
  isCertTolerant ? isCertTolerant === 'true' : undefined
@@ -100,9 +100,10 @@ else if (fn === 'dirWatch' && fnArgs.length === 2) {
100
100
  }
101
101
  else if (fn === 'netWatch' && [2, 3].includes(fnArgs.length)) {
102
102
  callNetWatch(... fnArgs)
103
- .then(() => {
103
+ .then(isOK => {
104
104
  console.log('Network watch ended');
105
- process.exit(0);
105
+ // Exit with a failure code if the watch was misconfigured or aborted, so supervisors notice.
106
+ process.exit(isOK ? 0 : 1);
106
107
  });
107
108
  }
108
109
  else {
@@ -26,10 +26,16 @@ services:
26
26
  # Send nuVal requests to the sidecar instead of the W3C service.
27
27
  TESTARO_NU_URL: "http://nu:8888/?parser=html&out=json"
28
28
  # JODBIR and REPORTDIR from Dockerfile override those from .env.
29
- # Import other specific environment variables from .env file.
30
- AGENT: ${AGENT}
29
+ # Import other specific environment variables from .env file. The
30
+ # NETWATCH_* variables default to empty so that directory-watch use
31
+ # needs no .env entries for them; set them in .env for server polling.
31
32
  WAVE_KEY: ${WAVE_KEY}
32
33
  ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
34
+ NETWATCH_URL_JOB: ${NETWATCH_URL_JOB:-}
35
+ NETWATCH_URL_REPORT: ${NETWATCH_URL_REPORT:-}
36
+ NETWATCH_AUTH_TYPE: ${NETWATCH_AUTH_TYPE:-}
37
+ NETWATCH_WORKER_ID: ${NETWATCH_WORKER_ID:-}
38
+ NETWATCH_WORKER_SECRET: ${NETWATCH_WORKER_SECRET:-}
33
39
  volumes:
34
40
  # Host directories for jobs and reports. The container runs as the
35
41
  # unprivileged pwuser; the host directories must be writable by it
@@ -0,0 +1,199 @@
1
+ # Checkpoints: scanning across a serial action flow
2
+
3
+ **Status:** Phases 0, 1, and 1b shipped in Testaro 78.2 (2026-09-02); Phases 2 (`report.flow`),
4
+ 3 (`scope: 'changed'`), and 4 (incremental pruning, selector acts, the user-path converter)
5
+ shipped in 78.3 (2026-09-02). The interaction modalities and the yra-monitor ingestion changes
6
+ remain follow-ups.
7
+
8
+ ## Why
9
+
10
+ A job is a serial list of acts: launch a browser, interact with the page, test it. Until 78.2,
11
+ the interaction acts had no effect on any test: every `test` act ran in a child process that
12
+ launched its own browser and navigated to the job target, and the element catalog was built
13
+ once, before any act, for that same page. A job of the form launch → click → test therefore
14
+ tested the page as first loaded, and any test after a `url` act resolved its elements against
15
+ the wrong page's catalog.
16
+
17
+ The maintainer's goals: keep the serial action flow; keep or extend a running list of found
18
+ issues as the flow progresses; use explicit triggers for re-scanning rather than re-running
19
+ every tool after every action; and re-scan either the whole page or only the parts that
20
+ changed, as the case requires.
21
+
22
+ ## The model
23
+
24
+ A **checkpoint** is a named page state reached by the flow, snapshotted and tested:
25
+
26
+ - **Checkpoint 0** is the job target as launched. `getCatalog` creates it before any act, as
27
+ it always built the catalog, and records it as `report.checkpoints[0]` (named `start`).
28
+ - A **`checkpoint` act** (`{type: 'checkpoint', which: '<name>'}`) creates the next one from
29
+ the live page in the parent process: catalog entries for the current state, page image(s)
30
+ if the job asks for them, the URL and title, an ARIA snapshot, and a DOM digest.
31
+ - A **`test` act belongs to the most recent checkpoint** (`act.checkpoint`), and every
32
+ standard instance it reports carries `checkpoint`.
33
+
34
+ Checkpoint mode is opt-in: it is on only when the job contains a `checkpoint` act. Legacy
35
+ jobs (no such act) run as before and produce today's report plus the `checkpoint: 0` fields;
36
+ if interaction acts precede a test act in a legacy job, `jobData.warnings` says that the act
37
+ tests checkpoint 0. In checkpoint mode, interaction acts followed by a test act without an
38
+ intervening checkpoint produce an implicit checkpoint (named `act<N>`) and a warning; the
39
+ warning never fails the job.
40
+
41
+ ### Navigation and interaction checkpoints
42
+
43
+ A checkpoint is a **navigation checkpoint** when the page was reached by navigation alone
44
+ (a `launch` or `url` act) and no interaction act ran since; it is an **interaction
45
+ checkpoint** when acts on a page (button, link, text, press, reveal, …) reached it. The
46
+ distinction decides how a test act's browser reaches the state:
47
+
48
+ - Navigation checkpoint: the child navigates to the checkpoint's URL. Every tool runs.
49
+ - Interaction checkpoint: the child navigates to the URL the interaction started from
50
+ (`launchURL`) and **replays** the recorded interaction acts (`replay`, act indexes) before
51
+ running the tool. The replay happens inside `launchOnce` after navigation and before any
52
+ `data-xpath` stamping, so elements the acts reveal or create are stamped like the rest,
53
+ and every per-rule relaunch of a contaminating testaro rule replays automatically.
54
+
55
+ Replay is a re-enactment, not a restore. Server state, sessions, timers and randomness can
56
+ make the replayed DOM differ from the snapshot. Each test act records
57
+ `data.replay = {checkpoint, acts, elapsedMs, fidelity}` where `fidelity` is `exact` when
58
+ the replayed DOM's digest equals the checkpoint's, else `divergent`. A replay failure
59
+ prevents the test act with `checkpoint replay failed at act N (…)` and is not retried.
60
+
61
+ ### What each tool can observe
62
+
63
+ `procs/job.js` `toolInputs` declares what a tool tests:
64
+
65
+ | input | tools | interaction checkpoints |
66
+ | --- | --- | --- |
67
+ | `page` | alfa, aslint, axe, ed11y, htmlcs, ibm, pour, surea11y, testaro | yes (replayed page) |
68
+ | `html` | qualWeb, nuVal, nuVnu (they read the live page's HTML) | yes (replayed page) |
69
+ | `url` | wave; nuVal and nuVnu with `withSource: true` | no: prevented with a stated reason |
70
+
71
+ ### Report shape
72
+
73
+ - `report.checkpoints[k]`: `{index, name, implicit, actIndex, launchActIndex, launchURL,
74
+ replay, interaction: {modality}, kind: 'navigation' | 'interaction', url, title,
75
+ imageIndexes, catalogRange, elementCount, ariaSnapshot, domDigest, elapsedMs, testActs}`.
76
+ - `report.catalog` stays one flat map. Entries carry `checkpoint`; indexes come from a
77
+ monotonic job-time counter (`report.catalogNextIndex`), so entries of different checkpoints
78
+ never collide, and the job-time `report.pathIDs` is scoped per checkpoint so the same XPath
79
+ in two states maps to two entries. Pruning keeps only cited entries, as before.
80
+ - `report.images`: `images[0]` (and `[1]` at `imageScale > 1`) keep meaning checkpoint 0;
81
+ later checkpoints' images are indexed by `checkpoints[k].imageIndexes`. The testaro `motion`
82
+ rule compares against its checkpoint's image.
83
+ - `jobData.catalogData.checkpoints[k]`: element and entry counts per checkpoint.
84
+ - `report.flow` (two or more checkpoints): `{checkpoints: [{index, name, kind, url, actIndex,
85
+ testActs, tools, issueCount}], deltas: [{from, to, tools, notObserved, added, persisted,
86
+ removed, notRetested, structure, aria}]}`; each issue is `{tool, ruleID, pathID, startTag,
87
+ what, ordinalSeverity, outcome, count, actIndexes}`.
88
+ - Test acts: `scope` (`page` default or `changed`) and `data.scope = {requested, applied,
89
+ reason, roots, pathIDs, commonRoot, localRules?, pageRules?}`. Job-time only, deleted by
90
+ `pruneCatalog`: `report.scope`, `report.ruleScopeRoots`.
91
+ - Progress events: `checkpointStart` and `checkpointEnd` (with `kind` and `elapsedMs`);
92
+ `actEnd` carries `checkpoint`.
93
+
94
+ ### Interaction modality (planned)
95
+
96
+ The act executors live in `procs/actDo.js` and take an `interaction` option
97
+ (`{modality: 'efficient'}` today), recorded on each checkpoint so replay uses the same
98
+ modality. Low-level events (`mousedown`/`mouseup`), emulated human mouse travel and key
99
+ pauses, and pass-to-pass comparisons between modalities are designed to slot in there.
100
+
101
+ ## Phases
102
+
103
+ - **Phase 0 (shipped):** act-loop repairs (`next` acts, failed launches, the `url` and
104
+ `state` acts), `procs/actDo.js`, `procs/xPathScript.js`, `catalogPage`, checkpoint 0.
105
+ - **Phase 1 (shipped):** the `checkpoint` act, `procs/checkpoint.js`, replay in the
106
+ launcher, implicit checkpoints, tool gating, the `validation/tests/jobProperties/checkpoint`
107
+ validator with report-level expectations.
108
+ - **Phase 1b (shipped):** the `isolation` job property (or the `ISOLATION` environment
109
+ default): `process` (the default: a child process and browser per test act), `browser`
110
+ (one browser shared by the job's launches, a fresh context per test act, replay), `page`
111
+ (tools run in sequence in the job's process on the live checkpoint page, no replay;
112
+ contaminating testaro rules still get a fresh context with replay). `procs/testAct.js`
113
+ performs a test act for both the child process and the in-process levels; the launcher
114
+ keeps the shared browser and prepares a live page (XPath script or attributes, accessible
115
+ names) without changing its DOM. Recorded in `jobData.isolation`. Only `process` can kill a
116
+ tool that overruns its time limit; the others report the act as timed out and continue.
117
+ - **Phase 2 (shipped):** `report.flow` (`procs/flow.js`, `getFlow(report)`), added at job end
118
+ before pruning when the job has two or more checkpoints, and usable standalone on a stored
119
+ report (the structure diff then covers cited elements only). `flow.checkpoints[k]`
120
+ summarizes each checkpoint; `flow.deltas[k - 1]` compares checkpoints `k - 1` and `k`:
121
+ `added`, `persisted`, and `removed` issues with identity `tool | ruleID | pathID | startTag`
122
+ (box and text excluded, since layout and copy shift without the defect changing; a summary
123
+ instance has an empty element key), compared only for the `tools` that observed both
124
+ checkpoints with a non-prevented act (`notObserved` lists the rest); `structure`, the
125
+ catalog diff (`procs/scope.js`, `getStructureDiff`): `added`, `removed`, and `changed`
126
+ (start tag) XPaths, `textChanged` XPaths, and `roots`, the outermost changed elements (the
127
+ nearest surviving ancestor stands for a removed element; a text change counts only where no
128
+ descendant changed, because the catalog text of an element includes its descendants'); and
129
+ `aria`, a line diff of the ARIA snapshots (`diff`, the jsdiff library yra-monitor also uses;
130
+ at most 500 changed lines recorded, `truncated` otherwise).
131
+ - **Phase 3 (shipped):** `scope: 'page' | 'changed'` on test acts (`actSpecs.js`; a job with a
132
+ changed-scope act must have a checkpoint act). Before such an act the acts loop calls
133
+ `getChangedRoots(report, k)`, which converts the structure roots of the diff between
134
+ checkpoints `k - 1` and `k` to CSS selectors (`/html/body/main[1]/div[2]` becomes
135
+ `html > body > main:nth-of-type(1) > div:nth-of-type(2)`, exact because `getXPath`
136
+ subscripts among same-tag siblings, which is what `nth-of-type` counts) and gives them to
137
+ the tool as the job-time `report.scope` when the tool is in `toolScopes` (`procs/job.js`:
138
+ axe, surea11y, testaro). The act records `data.scope`; when the act cannot be scoped (no
139
+ previous checkpoint, no change, more than `maxRoots` = 50 roots, an XPath with no selector,
140
+ a tool without a root option) it tests the whole page with `applied: false` and a reason,
141
+ and `jobData.warnings` says so. axe takes the roots as its `include` context; surea11y takes
142
+ the nearest common ancestor of the roots (`commonRoot`), since its context is one selector;
143
+ the testaro tool sets `report.ruleScopeRoots` per rule to the roots for a rule with
144
+ `local: true` in `allRules` and to null otherwise, and `doTest` keeps only candidates inside
145
+ a root (`getBasicResult` rules, hover and role, are not local). The conservative first-pass
146
+ classification marks as not local: allHidden, bulk, docType, dupAtt, headEl, headingAmb,
147
+ secHeading, linkAmb, labClash, radioSet, styleDiff, targetsNear, title, motion, role, hover,
148
+ hovInd, focAll, focAndOp, focInd, focVis, tabNav, buttonMenu, elements, textNodes. Whether a
149
+ partial scan is valid is not automated; the author chooses per act, and the rule metadata
150
+ protects the choice. Limits: the diff is by XPath, so inserting one sibling shifts later
151
+ same-tag indexes and the changed set can grow to the container (correct but coarse); a
152
+ changed-scope act finds only defects inside the roots, so `flow` unions the findings of every
153
+ act at a checkpoint rather than trusting one act.
154
+ - **Phase 4 (shipped):** three parts.
155
+ - *Incremental pruning.* When checkpoint `k + 1` is created, `makeCheckpoint` first records
156
+ the structure diff between `k` and `k + 1` on the new checkpoint (job-time
157
+ `checkpoints[k + 1].structure`, which `getChangedRoots` and `getFlow` read instead of
158
+ recomputing, and which the acts loop deletes once `flow` holds it), then calls
159
+ `pruneCheckpoint(report, k)` (`procs/catalog.ts`), which deletes checkpoint `k`'s entries
160
+ that no test act of checkpoint `k` cites and drops its job-time XPath map. No later test
161
+ act can cite checkpoint `k`, since a test act belongs to the latest checkpoint, so the
162
+ temporary report that every test act reads stays bounded by one checkpoint's page plus
163
+ the cited entries of earlier ones. `catalogData.elementCount` is therefore the count of
164
+ entries ever made (`catalogNextIndex`), not the count at pruning time. A single-checkpoint
165
+ job is unaffected.
166
+ - *Selector acts.* The move acts (`button`, `checkbox`, `link`, `radio`, `search`, `select`,
167
+ `text`) accept a `selector` property, any Playwright selector (CSS, `text=`, `role=`,
168
+ `label=`), which replaces the act type's element selector and text matching; `index`
169
+ still picks among matches. A `text` act with `clear: true` replaces the input's value
170
+ instead of appending to it, and a `select` act whose `what` matches no option text falls
171
+ back to Playwright's value-or-label matching. Replay uses the same executors, so
172
+ selector acts replay like the rest.
173
+ - *User-path converter.* `procs/userPath.js` `getUserPathActs({startUrl, actions,
174
+ testActs, scanAtCheckpoints, what})` maps a recorded user path of the shape yra-monitor
175
+ stores (actions `click`, `fill`, `select`, `navigate`, `wait`, `checkpoint`, with
176
+ Playwright selectors) to a job's acts: launch, then one act per action (`button`, `text`
177
+ with `clear`, `select`, `url`, `state: idle` for a pause), and for each `checkpoint`
178
+ action a `checkpoint` act (named from its label, made unique) followed by a copy of each
179
+ requested test act, with `scope: 'changed'` downgraded to `page` at the first checkpoint.
180
+ The `userPath` validator runs the converter's output for a fill, select, checkpoint,
181
+ click, wait, checkpoint, navigate, checkpoint path. yra-monitor's user-path executor still
182
+ replays actions itself and runs a stub axe scan at checkpoints; replacing that with a
183
+ Testaro job built by this converter waits on the ingestion changes below.
184
+ - `flow` and scoped acts: a tool whose acts at a checkpoint were all scoped to changed
185
+ subtrees observed nothing outside them, so an earlier issue of that tool whose element
186
+ lies outside the roots is listed in the delta as `notRetested`, not `removed`.
187
+
188
+ ## Relationship to yra-monitor
189
+
190
+ yra-monitor's DOM-difference feature captures outerHTML, a screenshot, element positions
191
+ and the ARIA snapshot per URL per scan and diffs them across scans. Checkpoints capture the
192
+ same artifacts per page state inside one job. The cross-scan history and comparison UI stay
193
+ downstream; a later consolidation can store Testaro's checkpoint-0 artifacts instead of
194
+ launching a separate capture browser.
195
+
196
+ Until yra-monitor carries `checkpoint` and the checkpoint URL on stored rows, adds a
197
+ checkpoint dimension to its unique-issue key and auto-resolve scoping, and stores one
198
+ screenshot per checkpoint, multi-checkpoint reports must not be fed through its scan
199
+ ingestion path.