testaro 78.0.8 → 78.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/.gitattributes +94 -0
  2. package/.github/workflows/ci.yml +58 -0
  3. package/.github/workflows/publish.yml +50 -0
  4. package/.github/workflows/typescript.yml +35 -0
  5. package/AGENTS.md +2 -2
  6. package/CLAUDE.md +24 -18
  7. package/CONTAINERS.md +9 -6
  8. package/CONTRIBUTING.md +29 -3
  9. package/Dockerfile +1 -1
  10. package/README.md +143 -19
  11. package/UPGRADES.md +4 -0
  12. package/actSpecs-doc.md +13 -1
  13. package/actSpecs.js +29 -5
  14. package/call.js +6 -5
  15. package/dist/nameComputation.js +1 -0
  16. package/docker-compose.yml +8 -2
  17. package/docs/checkpoint-scanning.md +199 -0
  18. package/docs/standard-result-outcome.md +165 -0
  19. package/env.example +49 -10
  20. package/eslint.config.mjs +101 -0
  21. package/netWatch.js +372 -222
  22. package/package.json +46 -31
  23. package/pour/README.md +47 -0
  24. package/pour/pour.min.js +8 -0
  25. package/procs/actDo.js +808 -0
  26. package/procs/catalog.d.ts +17 -0
  27. package/procs/catalog.js +312 -221
  28. package/procs/catalog.ts +398 -0
  29. package/procs/checkpoint.js +110 -0
  30. package/procs/config.d.ts +41 -0
  31. package/procs/config.js +98 -0
  32. package/procs/dateTime.js +2 -1
  33. package/procs/doActs.js +442 -849
  34. package/procs/doTestAct.js +27 -121
  35. package/procs/flow.js +221 -0
  36. package/procs/generateRuleRegistry.js +94 -0
  37. package/procs/getSource.d.ts +26 -0
  38. package/procs/job.js +115 -1
  39. package/procs/launch.d.ts +40 -0
  40. package/procs/launch.js +243 -108
  41. package/procs/nu.d.ts +48 -0
  42. package/procs/scope.js +196 -0
  43. package/procs/shoot.d.ts +33 -0
  44. package/procs/standard.d.ts +18 -0
  45. package/procs/standard.js +83 -0
  46. package/procs/standard.ts +126 -0
  47. package/procs/testAct.js +145 -0
  48. package/procs/testaro.d.ts +20 -0
  49. package/procs/testaro.js +236 -189
  50. package/procs/testaro.ts +321 -0
  51. package/procs/userPath.js +126 -0
  52. package/procs/xPath.d.ts +4 -0
  53. package/procs/xPath.js +81 -69
  54. package/procs/xPath.ts +106 -0
  55. package/procs/xPathScript.d.ts +6 -0
  56. package/procs/xPathScript.js +70 -0
  57. package/run.js +3 -2
  58. package/surea11y/README.md +44 -0
  59. package/surea11y/surea11y.browser.js +14 -0
  60. package/testaro/adbID.d.ts +3 -0
  61. package/testaro/adbID.js +38 -41
  62. package/testaro/adbID.ts +66 -0
  63. package/testaro/allCapStyle.d.ts +3 -0
  64. package/testaro/allCapStyle.js +31 -34
  65. package/testaro/allCapStyle.ts +55 -0
  66. package/testaro/allCaps.d.ts +16 -0
  67. package/testaro/allCaps.js +179 -151
  68. package/testaro/allCaps.ts +228 -0
  69. package/testaro/allHidden.d.ts +13 -0
  70. package/testaro/allHidden.js +29 -30
  71. package/testaro/allHidden.ts +50 -0
  72. package/testaro/allSlanted.d.ts +3 -0
  73. package/testaro/allSlanted.js +30 -33
  74. package/testaro/allSlanted.ts +54 -0
  75. package/testaro/altScheme.d.ts +3 -0
  76. package/testaro/altScheme.js +27 -30
  77. package/testaro/altScheme.ts +50 -0
  78. package/testaro/attVal.d.ts +3 -0
  79. package/testaro/attVal.js +20 -21
  80. package/testaro/attVal.ts +52 -0
  81. package/testaro/autocomplete.d.ts +3 -0
  82. package/testaro/autocomplete.js +59 -75
  83. package/testaro/autocomplete.ts +101 -0
  84. package/testaro/bulk.d.ts +13 -0
  85. package/testaro/bulk.js +32 -33
  86. package/testaro/bulk.ts +55 -0
  87. package/testaro/buttonMenu.d.ts +9 -0
  88. package/testaro/buttonMenu.js +317 -319
  89. package/testaro/buttonMenu.ts +391 -0
  90. package/testaro/captionLoc.d.ts +3 -0
  91. package/testaro/captionLoc.js +17 -20
  92. package/testaro/captionLoc.ts +40 -0
  93. package/testaro/datalistRef.d.ts +3 -0
  94. package/testaro/datalistRef.js +33 -36
  95. package/testaro/datalistRef.ts +55 -0
  96. package/testaro/distortion.d.ts +3 -0
  97. package/testaro/distortion.js +57 -26
  98. package/testaro/distortion.ts +81 -0
  99. package/testaro/docType.d.ts +15 -0
  100. package/testaro/docType.js +25 -25
  101. package/testaro/docType.ts +45 -0
  102. package/testaro/dupAtt.d.ts +16 -0
  103. package/testaro/dupAtt.js +113 -104
  104. package/testaro/dupAtt.ts +144 -0
  105. package/testaro/elements.d.ts +6 -0
  106. package/testaro/elements.js +153 -153
  107. package/testaro/elements.ts +215 -0
  108. package/testaro/embAc.d.ts +3 -0
  109. package/testaro/embAc.js +19 -20
  110. package/testaro/embAc.ts +40 -0
  111. package/testaro/focAll.d.ts +13 -0
  112. package/testaro/focAll.js +185 -191
  113. package/testaro/focAll.ts +217 -0
  114. package/testaro/focAndOp.d.ts +3 -0
  115. package/testaro/focAndOp.js +100 -104
  116. package/testaro/focAndOp.ts +128 -0
  117. package/testaro/focInd.d.ts +3 -0
  118. package/testaro/focInd.js +64 -66
  119. package/testaro/focInd.ts +96 -0
  120. package/testaro/focVis.d.ts +3 -0
  121. package/testaro/focVis.js +28 -30
  122. package/testaro/focVis.ts +52 -0
  123. package/testaro/headEl.d.ts +10 -0
  124. package/testaro/headEl.js +61 -62
  125. package/testaro/headEl.ts +82 -0
  126. package/testaro/headingAmb.d.ts +3 -0
  127. package/testaro/headingAmb.js +54 -63
  128. package/testaro/headingAmb.ts +72 -0
  129. package/testaro/hovInd.d.ts +12 -0
  130. package/testaro/hovInd.js +163 -130
  131. package/testaro/hovInd.ts +199 -0
  132. package/testaro/hover.d.ts +3 -0
  133. package/testaro/hover.js +155 -126
  134. package/testaro/hover.ts +154 -0
  135. package/testaro/hr.d.ts +3 -0
  136. package/testaro/hr.js +14 -17
  137. package/testaro/hr.ts +36 -0
  138. package/testaro/imageLink.d.ts +3 -0
  139. package/testaro/imageLink.js +17 -20
  140. package/testaro/imageLink.ts +42 -0
  141. package/testaro/labClash.d.ts +3 -0
  142. package/testaro/labClash.js +31 -33
  143. package/testaro/labClash.ts +54 -0
  144. package/testaro/legendLoc.d.ts +3 -0
  145. package/testaro/legendLoc.js +17 -20
  146. package/testaro/legendLoc.ts +42 -0
  147. package/testaro/lineHeight.d.ts +3 -0
  148. package/testaro/lineHeight.js +43 -48
  149. package/testaro/lineHeight.ts +70 -0
  150. package/testaro/linkAmb.d.ts +7 -0
  151. package/testaro/linkAmb.js +79 -80
  152. package/testaro/linkAmb.ts +105 -0
  153. package/testaro/linkExt.d.ts +3 -0
  154. package/testaro/linkExt.js +13 -16
  155. package/testaro/linkExt.ts +35 -0
  156. package/testaro/linkOldAtt.d.ts +3 -0
  157. package/testaro/linkOldAtt.js +25 -28
  158. package/testaro/linkOldAtt.ts +48 -0
  159. package/testaro/linkTo.d.ts +3 -0
  160. package/testaro/linkTo.js +22 -22
  161. package/testaro/linkTo.ts +43 -0
  162. package/testaro/linkUl.d.ts +3 -0
  163. package/testaro/linkUl.js +30 -34
  164. package/testaro/linkUl.ts +54 -0
  165. package/testaro/miniText.d.ts +3 -0
  166. package/testaro/miniText.js +41 -44
  167. package/testaro/miniText.ts +68 -0
  168. package/testaro/motion.d.ts +10 -0
  169. package/testaro/motion.js +92 -96
  170. package/testaro/motion.ts +125 -0
  171. package/testaro/nonTable.d.ts +3 -0
  172. package/testaro/nonTable.js +39 -45
  173. package/testaro/nonTable.ts +66 -0
  174. package/testaro/optRoleSel.d.ts +3 -0
  175. package/testaro/optRoleSel.js +16 -19
  176. package/testaro/optRoleSel.ts +41 -0
  177. package/testaro/phOnly.d.ts +3 -0
  178. package/testaro/phOnly.js +18 -21
  179. package/testaro/phOnly.ts +43 -0
  180. package/testaro/pseudoP.d.ts +3 -0
  181. package/testaro/pseudoP.js +36 -38
  182. package/testaro/pseudoP.ts +59 -0
  183. package/testaro/radioSet.d.ts +3 -0
  184. package/testaro/radioSet.js +57 -59
  185. package/testaro/radioSet.ts +79 -0
  186. package/testaro/registry.d.ts +63 -0
  187. package/testaro/registry.js +67 -0
  188. package/testaro/registry.ts +141 -0
  189. package/testaro/role.d.ts +3 -0
  190. package/testaro/role.js +27 -29
  191. package/testaro/role.ts +53 -0
  192. package/testaro/secHeading.d.ts +3 -0
  193. package/testaro/secHeading.js +30 -33
  194. package/testaro/secHeading.ts +53 -0
  195. package/testaro/styleDiff.d.ts +25 -0
  196. package/testaro/styleDiff.js +248 -252
  197. package/testaro/styleDiff.ts +303 -0
  198. package/testaro/tabNav.d.ts +33 -0
  199. package/testaro/tabNav.js +272 -342
  200. package/testaro/tabNav.ts +454 -0
  201. package/testaro/targetsNear.d.ts +9 -0
  202. package/testaro/targetsNear.js +130 -132
  203. package/testaro/targetsNear.ts +160 -0
  204. package/testaro/textNodes.d.ts +6 -0
  205. package/testaro/textNodes.js +139 -135
  206. package/testaro/textNodes.ts +185 -0
  207. package/testaro/textSem.d.ts +3 -0
  208. package/testaro/textSem.js +25 -28
  209. package/testaro/textSem.ts +47 -0
  210. package/testaro/title.d.ts +9 -0
  211. package/testaro/title.js +16 -13
  212. package/testaro/title.ts +31 -0
  213. package/testaro/titledEl.d.ts +3 -0
  214. package/testaro/titledEl.js +15 -18
  215. package/testaro/titledEl.ts +38 -0
  216. package/testaro/zIndex.d.ts +3 -0
  217. package/testaro/zIndex.js +19 -22
  218. package/testaro/zIndex.ts +42 -0
  219. package/tests/alfa.d.ts +45 -0
  220. package/tests/alfa.js +138 -141
  221. package/tests/alfa.ts +214 -0
  222. package/tests/aslint.d.ts +33 -0
  223. package/tests/aslint.js +272 -249
  224. package/tests/aslint.ts +301 -0
  225. package/tests/axe.d.ts +27 -0
  226. package/tests/axe.js +199 -200
  227. package/tests/axe.ts +277 -0
  228. package/tests/ed11y.d.ts +28 -0
  229. package/tests/ed11y.js +141 -99
  230. package/tests/ed11y.ts +178 -0
  231. package/tests/htmlcs.d.ts +21 -0
  232. package/tests/htmlcs.js +174 -140
  233. package/tests/htmlcs.ts +181 -0
  234. package/tests/ibm.d.ts +57 -0
  235. package/tests/ibm.js +182 -175
  236. package/tests/ibm.ts +287 -0
  237. package/tests/nuVal.d.ts +13 -0
  238. package/tests/nuVal.js +107 -112
  239. package/tests/nuVal.ts +145 -0
  240. package/tests/nuVnu.d.ts +15 -0
  241. package/tests/nuVnu.js +141 -111
  242. package/tests/nuVnu.ts +144 -0
  243. package/tests/pour.d.ts +31 -0
  244. package/tests/pour.js +242 -0
  245. package/tests/pour.ts +273 -0
  246. package/tests/qualWeb.d.ts +39 -0
  247. package/tests/qualWeb.js +262 -273
  248. package/tests/qualWeb.ts +344 -0
  249. package/tests/surea11y.d.ts +33 -0
  250. package/tests/surea11y.js +288 -0
  251. package/tests/surea11y.ts +334 -0
  252. package/tests/testaro.d.ts +25 -0
  253. package/tests/testaro.js +745 -652
  254. package/tests/testaro.ts +862 -0
  255. package/tests/wave.d.ts +46 -0
  256. package/tests/wave.js +166 -177
  257. package/tests/wave.ts +252 -0
  258. package/tsconfig.json +17 -0
  259. package/types.d.ts +243 -0
  260. package/types.js +10 -0
  261. package/types.ts +376 -0
  262. package/validation/act/README.md +46 -0
  263. package/validation/act/capture.js +877 -0
  264. package/validation/act/chromium-issue-draft.md +66 -0
  265. package/validation/act/fp-triage-2026-08-22.md +86 -0
  266. package/validation/act/isolation-notes.md +159 -0
  267. package/validation/act/playwright-issue-draft.md +73 -0
  268. package/validation/act/propose-mappings.js +0 -0
  269. package/validation/act/repro-cdp-raw.js +105 -0
  270. package/validation/act/repro-metarefresh.js +65 -0
  271. package/validation/act/score.js +184 -0
  272. package/validation/act/stage3a-stress-report.md +69 -0
  273. package/validation/act/stage3b-mapping-proposals.md +94 -0
  274. package/validation/act/stage3b-triage-draft.md +138 -0
  275. package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
  276. package/validation/executors/netWatch.js +180 -90
  277. package/validation/executors/test.js +17 -2
  278. package/validation/executors/tests.js +118 -10
  279. package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
  280. package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
  281. package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
  282. package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
  283. package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
  284. package/validation/knownFailures.json +1 -0
  285. package/validation/tests/jobProperties/adbID.json +27 -2
  286. package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
  287. package/validation/tests/jobProperties/allCaps.json +25 -0
  288. package/validation/tests/jobProperties/allHidden.json +133 -13
  289. package/validation/tests/jobProperties/allSlanted.json +3 -3
  290. package/validation/tests/jobProperties/altScheme.json +23 -8
  291. package/validation/tests/jobProperties/attVal.json +57 -57
  292. package/validation/tests/jobProperties/autocomplete.json +11 -1
  293. package/validation/tests/jobProperties/bulk.json +6 -1
  294. package/validation/tests/jobProperties/buttonMenu.json +63 -42
  295. package/validation/tests/jobProperties/captionLoc.json +1 -6
  296. package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
  297. package/validation/tests/jobProperties/checkpoint-page.json +397 -0
  298. package/validation/tests/jobProperties/checkpoint.json +395 -0
  299. package/validation/tests/jobProperties/datalistRef.json +20 -5
  300. package/validation/tests/jobProperties/distortion.json +28 -3
  301. package/validation/tests/jobProperties/docType.json +2 -2
  302. package/validation/tests/jobProperties/dupAtt.json +48 -33
  303. package/validation/tests/jobProperties/elements.json +28 -28
  304. package/validation/tests/jobProperties/embAc.json +36 -31
  305. package/validation/tests/jobProperties/focAndOp.json +284 -0
  306. package/validation/tests/jobProperties/focInd.json +34 -34
  307. package/validation/tests/jobProperties/focVis.json +1 -1
  308. package/validation/tests/jobProperties/hover.json +39 -37
  309. package/validation/tests/jobProperties/hr.json +13 -3
  310. package/validation/tests/jobProperties/imageLink.json +0 -5
  311. package/validation/tests/jobProperties/labClash.json +68 -33
  312. package/validation/tests/jobProperties/legendLoc.json +1 -6
  313. package/validation/tests/jobProperties/lineHeight.json +2 -2
  314. package/validation/tests/jobProperties/linkAmb.json +13 -18
  315. package/validation/tests/jobProperties/linkExt.json +1 -1
  316. package/validation/tests/jobProperties/linkOldAtt.json +12 -2
  317. package/validation/tests/jobProperties/linkTo.json +1 -1
  318. package/validation/tests/jobProperties/linkUl.json +65 -65
  319. package/validation/tests/jobProperties/miniText.json +7 -2
  320. package/validation/tests/jobProperties/motion.json +4 -50
  321. package/validation/tests/jobProperties/nonTable.json +48 -3
  322. package/validation/tests/jobProperties/optRoleSel.json +12 -2
  323. package/validation/tests/jobProperties/phOnly.json +6 -16
  324. package/validation/tests/jobProperties/pseudoP.json +17 -2
  325. package/validation/tests/jobProperties/radioSet.json +30 -30
  326. package/validation/tests/jobProperties/role.json +25 -5
  327. package/validation/tests/jobProperties/secHeading.json +26 -21
  328. package/validation/tests/jobProperties/styleDiff.json +35 -5
  329. package/validation/tests/jobProperties/tabNav.json +3 -1
  330. package/validation/tests/jobProperties/targetsNear.json +237 -0
  331. package/validation/tests/jobProperties/textNodes.json +29 -29
  332. package/validation/tests/jobProperties/textSem.json +26 -1
  333. package/validation/tests/jobProperties/title.json +12 -2
  334. package/validation/tests/jobProperties/titledEl.json +39 -9
  335. package/validation/tests/jobProperties/userPath.json +317 -0
  336. package/validation/tests/jobProperties/zIndex.json +40 -35
  337. package/validation/tests/targets/allCapStyle/index.html +26 -0
  338. package/validation/tests/targets/checkpoint/index.html +38 -0
  339. package/validation/tests/targets/datalistRef/index.html +1 -1
  340. package/validation/tests/targets/focAndOp/bad.html +29 -0
  341. package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
  342. package/validation/tests/targets/focInd/bad.html +2 -1
  343. package/validation/tests/targets/headEl/index.html +10 -1
  344. package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
  345. package/validation/tests/targets/userPath/index.html +42 -0
  346. package/validation/validateTest.js +61 -9
  347. package/.claude/settings.local.json +0 -11
  348. package/.eslintrc.json +0 -41
  349. package/htmlcs/.eslintrc.json +0 -67
  350. package/validation/tests/jobProperties/linkTitle.json +0 -127
  351. package/validation/tests/jobProperties/opFoc.json +0 -164
  352. package/validation/tests/jobProperties/targetSmall.json +0 -152
  353. package/validation/tests/jobProperties/targetTiny.json +0 -142
  354. package/validation/tests/targets/focOp/bad.html +0 -25
  355. package/validation/tests/targets/linkTitle/index.html +0 -24
  356. package/validation/tests/targets/opFoc/bad.html +0 -26
  357. package/validation/tests/targets/opFoc/good.html +0 -23
package/tests/qualWeb.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
4
  © 2026 Jeff Witt.
@@ -8,297 +9,285 @@
8
9
 
9
10
  SPDX-License-Identifier: MIT
10
11
  */
11
-
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.reporter = void 0;
14
+ const xPath_1 = require("../procs/xPath");
15
+ const standard_1 = require("../procs/standard");
16
+ const fs_1 = require("fs");
17
+ // CONSTANTS
12
18
  /*
13
- qualWeb
14
- Implements the QualWeb ruleset for accessibility.
19
+ QualWeb runs in the page under test, as @qualweb/core runs it in its own browser: the
20
+ page-side bundles first, then one bundle per section. It used to be given page.content() and
21
+ run by @qualweb/core, which loads that HTML with setContent() into a blank page — so relative
22
+ stylesheet links resolved nowhere, the page it tested was unstyled, and every CSS-dependent
23
+ rule (contrast, visibility, text spacing…) judged a page the user never sees. The bundles are
24
+ evaluated (not added as script tags), so the page's Content Security Policy does not apply.
15
25
  */
16
-
17
- // IMPORTS
18
-
19
- const {QualWeb} = require('@qualweb/core');
20
- const {ACTRules} = require('@qualweb/act-rules');
21
- const {WCAGTechniques} = require('@qualweb/wcag-techniques');
22
- const {BestPractices} = require('@qualweb/best-practices');
23
- const {PlaywrightDriver} = require('@qualweb/playwright-driver');
24
- const {getAttributeXPath, getXPathCatalogIndex} = require('../procs/xPath');
25
-
26
- // CONSTANTS
27
-
28
- // QualWeb core engine with Playwright as driver.
29
- const qualWeb = new QualWeb(undefined, new PlaywrightDriver({
30
- adBlock: true,
31
- stealth: true
32
- }));
33
- const actRulesModule = new ACTRules({});
34
- const wcagModule = new WCAGTechniques({});
35
- const bpModule = new BestPractices({});
26
+ const readBundle = (name) => (0, fs_1.readFileSync)(require.resolve(name), 'utf8');
27
+ let pageBundles = null;
28
+ const sectionBundles = {};
29
+ const sectionPackages = {
30
+ 'act-rules': '@qualweb/act-rules',
31
+ 'wcag-techniques': '@qualweb/wcag-techniques',
32
+ 'best-practices': '@qualweb/best-practices'
33
+ };
34
+ // Rules a section may exclude after they crash before the section is given up on.
35
+ const MAX_EXCLUDED = 5;
36
36
  // Mapping of QualWeb module violation types to ordinal severities.
37
37
  const ordinalSeverities = {
38
- 'act-rules': {
39
- 'warning': 1,
40
- 'failed': 3
41
- },
42
- 'wcag-techniques': {
43
- 'warning': 0,
44
- 'failed': 2
45
- },
46
- 'best-practices': {
47
- 'warning': 0,
48
- 'failed': 1
49
- }
50
- }
51
-
52
- // FUNCTIONS
53
-
54
- // Conducts and reports the QualWeb tests.
55
- exports.reporter = async (page, report, actIndex, timeLimit) => {
56
- const act = report.acts[actIndex];
57
- const {rules} = act;
58
- const clusterOptions = {
59
- maxConcurrency: 1,
60
- timeout: timeLimit * 1000,
61
- monitor: false
62
- };
63
- // Initialize the act report.
64
- const data = {};
65
- const result = {
66
- nativeResult: {},
67
- standardResult: {}
68
- };
69
- const standard = report.standard !== 'no';
70
- // If standard results are to be reported:
71
- if (standard) {
72
- // Initialize the standard result.
73
- result.standardResult = {
74
- prevented: false,
75
- totals: [0, 0, 0, 0],
76
- instances: []
77
- };
78
- }
79
- try {
80
- // Start the QualWeb core engine, which launches a Playwright browser.
81
- await qualWeb.start(clusterOptions);
82
- }
83
- // If the start fails:
84
- catch(error) {
85
- return {
86
- data: {
87
- prevented: true,
88
- error: `Core engine start failed (${error.message})`
89
- },
90
- result
91
- };
92
- }
93
- // Otherwise, i.e. if the start succeeds, specify the invariant test options.
94
- const qualWebOptions = {
95
- log: {
96
- console: false,
97
- file: false
38
+ 'act-rules': {
39
+ 'warning': 1,
40
+ 'failed': 3
98
41
  },
99
- crawlOptions: {
100
- maxDepth: 0,
101
- maxUrls: 1,
102
- timeout: timeLimit * 1000,
103
- maxParallelCrawls: 1,
104
- logging: true
42
+ 'wcag-techniques': {
43
+ 'warning': 0,
44
+ 'failed': 2
105
45
  },
106
- execute: {
107
- counter: true
108
- },
109
- modules: []
110
- };
111
- try {
112
- // Provide the page content, including the data-xpath attributes.
113
- qualWebOptions.html = await page.content();
114
- // Specify which rules to test for, adding a custom execute property for report processing.
115
- const actSpec = rules ? rules.find(typeRules => typeRules.startsWith('act:')) : null;
116
- const wcagSpec = rules ? rules.find(typeRules => typeRules.startsWith('wcag:')) : null;
117
- const bestSpec = rules ? rules.find(typeRules => typeRules.startsWith('best:')) : null;
118
- if (actSpec) {
119
- if (actSpec === 'act:') {
120
- qualWebOptions.execute.act = false;
121
- }
122
- else {
123
- const actRules = actSpec.slice(4).split(',').map(num => `QW-ACT-R${num}`);
124
- qualWebOptions['act-rules'] = {rules: actRules};
125
- qualWebOptions.modules.push(actRulesModule);
126
- qualWebOptions.execute.act = true;
127
- }
46
+ 'best-practices': {
47
+ 'warning': 0,
48
+ 'failed': 1
128
49
  }
129
- else {
130
- qualWebOptions['act-rules'] = {
131
- levels: ['A', 'AA', 'AAA'],
132
- principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
133
- };
134
- qualWebOptions.modules.push(actRulesModule);
135
- qualWebOptions.execute.act = true;
50
+ };
51
+ /*
52
+ qualWeb
53
+ Implements the QualWeb ruleset for accessibility.
54
+ Compiled to qualWeb.js by tsc (issue #73); edit this file, not the emitted one.
55
+ */
56
+ // FUNCTIONS
57
+ // Conducts and reports the QualWeb tests.
58
+ const reporter = async (page, report, actIndex, timeLimit) => {
59
+ const act = report.acts[actIndex];
60
+ const { rules } = act;
61
+ // Initialize the act report.
62
+ const data = {};
63
+ const result = {
64
+ nativeResult: {},
65
+ standardResult: {}
66
+ };
67
+ const standard = report.standard !== 'no';
68
+ // If standard results are to be reported:
69
+ if (standard) {
70
+ // Initialize the standard result.
71
+ result.standardResult = (0, standard_1.getStandardResult)();
136
72
  }
137
- if (wcagSpec) {
138
- if (wcagSpec === 'wcag:') {
139
- qualWebOptions.execute.wcag = false;
140
- }
141
- else {
142
- const wcagTechniques = wcagSpec.slice(5).split(',').map(num => `QW-WCAG-T${num}`);
143
- qualWebOptions['wcag-techniques'] = {techniques: wcagTechniques};
144
- qualWebOptions.modules.push(wcagModule);
145
- qualWebOptions.execute.wcag = true;
146
- }
73
+ /*
74
+ QualWeb tests HTML documents only: on anything else (e.g. an SVG image) its rules report
75
+ false results such as a missing lang on the html element.
76
+ */
77
+ const rootNamespace = await page.evaluate(() => document.documentElement?.namespaceURI ?? null)
78
+ .catch(() => null);
79
+ // If the document is not HTML:
80
+ if (rootNamespace !== 'http://www.w3.org/1999/xhtml') {
81
+ // Report this.
82
+ return {
83
+ data: {
84
+ prevented: true,
85
+ error: `qualWeb tests HTML documents only (root namespace ${rootNamespace})`
86
+ },
87
+ result
88
+ };
147
89
  }
148
- else {
149
- qualWebOptions['wcag-techniques'] = {
90
+ // Specify, per section, whether and what to test (a rules spec of 'act:', 'wcag:' or 'best:'
91
+ // alone skips that section; with numbers, it tests only those rules).
92
+ const allAAA = {
150
93
  levels: ['A', 'AA', 'AAA'],
151
94
  principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
152
- };
153
- qualWebOptions.modules.push(wcagModule);
154
- qualWebOptions.execute.wcag = true;
155
- }
156
- if (bestSpec) {
157
- if (bestSpec === 'best:') {
158
- qualWebOptions.execute.bp = false;
159
- }
160
- else {
161
- const bestPractices = bestSpec.slice(5).split(',').map(num => `QW-BP${num}`);
162
- qualWebOptions['best-practices'] = {bestPractices};
163
- qualWebOptions.modules.push(bpModule);
164
- qualWebOptions.execute.bp = true;
165
- }
166
- }
167
- else {
168
- qualWebOptions['best-practices'] = {};
169
- qualWebOptions.modules.push(bpModule);
170
- qualWebOptions.execute.bp = true;
95
+ };
96
+ const sectionSpec = (prefix, codePrefix, all) => {
97
+ const spec = rules?.find(typeRules => typeRules.startsWith(prefix));
98
+ if (spec === undefined) {
99
+ return all;
100
+ }
101
+ if (spec === prefix) {
102
+ return null;
103
+ }
104
+ return { include: spec.slice(prefix.length).split(',').map(num => `${codePrefix}${num}`) };
105
+ };
106
+ const sections = {};
107
+ for (const [section, options] of [
108
+ ['act-rules', sectionSpec('act:', 'QW-ACT-R', allAAA)],
109
+ ['wcag-techniques', sectionSpec('wcag:', 'QW-WCAG-T', allAAA)],
110
+ ['best-practices', sectionSpec('best:', 'QW-BP', {})]
111
+ ]) {
112
+ if (options) {
113
+ sections[section] = options;
114
+ }
171
115
  }
172
- let qwReport;
116
+ const deadline = Date.now() + timeLimit * 1000;
173
117
  try {
174
- // Get the report.
175
- qwReport = await qualWeb.evaluate(qualWebOptions);
176
- }
177
- catch(error) {
178
- return {
179
- data: {
180
- prevented: true,
181
- error: `qualWeb evaluation failed (${error.message})`
182
- },
183
- result
184
- };
185
- }
186
- // Add the report to the result.
187
- result.nativeResult = qwReport.customHtml;
188
- const {nativeResult, standardResult} = result;
189
- // If the report contains, as it should, a copy of the DOM:
190
- if (nativeResult?.system?.page?.dom) {
191
- // Delete the copy for parsimony.
192
- delete nativeResult.system.page.dom;
193
- const {modules} = nativeResult;
194
- // If the report contains, as it should, a modules property:
195
- if (modules) {
196
- // For each test section in it:
197
- for (const section of ['act-rules', 'wcag-techniques', 'best-practices']) {
198
- // If testing in the section was specified:
199
- if (qualWebOptions[section]) {
200
- // If the section exists:
201
- if (modules[section]) {
202
- const {assertions} = modules[section];
203
- // If it contains assertions (test results):
204
- if (assertions) {
118
+ // Load QualWeb into the page.
119
+ pageBundles ??= ['@qualweb/qw-page', '@qualweb/util', '@qualweb/locale'].map(readBundle);
120
+ for (const source of pageBundles) {
121
+ await page.evaluate(source);
122
+ }
123
+ // The page's own source, which QualWeb's meta-element rules read.
124
+ const sourceHtml = await page.content();
125
+ const modules = {};
126
+ const rulePreventions = {};
127
+ // For each section to be tested:
128
+ for (const [section, options] of Object.entries(sections)) {
129
+ if (Date.now() > deadline) {
130
+ throw new Error(`time limit of ${timeLimit}s reached before ${section}`);
131
+ }
132
+ sectionBundles[section] ??= readBundle(sectionPackages[section]);
133
+ await page.evaluate(sectionBundles[section]);
134
+ /*
135
+ One rule that throws takes its whole section down with it (e.g. QW-ACT-R76 in 0.8.5 on
136
+ lab(), oklab(), color() or color-mix() colours, qualweb/qualweb#352), so a rule named in
137
+ the stack trace of a crash is excluded and the section rerun; a crash that names no rule
138
+ prevents only its section.
139
+ */
140
+ const outcome = await page.evaluate(({ section, options, sourceHtml, maxExcluded }) => {
141
+ // Only this section's bundle has been loaded, so name only its runner.
142
+ const Runner = section === 'act-rules'
143
+ // @ts-expect-error: defined by the section bundle evaluated above.
144
+ ? ACTRulesRunner
145
+ : section === 'wcag-techniques'
146
+ // @ts-expect-error: defined by the section bundle evaluated above.
147
+ ? WCAGTechniquesRunner
148
+ // @ts-expect-error: defined by the section bundle evaluated above.
149
+ : BestPracticesRunner;
150
+ const preventions = {};
151
+ const exclude = [];
152
+ for (;;) {
153
+ try {
154
+ const opts = exclude.length ? { ...options, exclude: [...(options.exclude ?? []), ...exclude] } : options;
155
+ const runner = new Runner(opts, 'en').configure(opts).test({ sourceHtml, newTabWasOpen: false });
156
+ window.__testaroQwRunners = { ...window.__testaroQwRunners, [section]: runner };
157
+ // QualWeb core keeps the act-rules runner here for its special-case pass.
158
+ if (section === 'act-rules') {
159
+ window.act = runner;
160
+ }
161
+ return { ok: true, preventions };
162
+ }
163
+ catch (error) {
164
+ const message = String(error?.message ?? error).slice(0, 200);
165
+ // Rule classes are named QW_ACT_R76, QW_WCAG_T23, QW_BP1.
166
+ const rule = String(error?.stack ?? '').match(/QW_(?:ACT_R|WCAG_T|BP)\d+/)?.[0].replace(/_/g, '-');
167
+ if (!rule || exclude.includes(rule) || exclude.length >= maxExcluded) {
168
+ preventions[section] = `section failed: ${message}`;
169
+ return { ok: false, preventions };
170
+ }
171
+ preventions[rule] = message;
172
+ exclude.push(rule);
173
+ }
174
+ }
175
+ }, { section, options, sourceHtml, maxExcluded: MAX_EXCLUDED });
176
+ Object.assign(rulePreventions, outcome.preventions);
177
+ if (!outcome.ok) {
178
+ continue;
179
+ }
180
+ /*
181
+ QualWeb core's special case for act-rules: QW-ACT-R40 (zoomed text not clipped) is
182
+ re-tested at a 640 × 512 viewport, then the viewport is restored.
183
+ */
184
+ const include = options.include;
185
+ if (section === 'act-rules' && (!include || include.includes('QW-ACT-R40'))) {
186
+ const viewport = page.viewportSize();
187
+ if (viewport) {
188
+ try {
189
+ await page.setViewportSize({ width: 640, height: 512 });
190
+ await page.evaluate(() => window.act?.testSpecial?.());
191
+ }
192
+ catch (error) {
193
+ rulePreventions['QW-ACT-R40'] = `special-case pass failed: ${String(error.message).slice(0, 200)}`;
194
+ }
195
+ finally {
196
+ await page.setViewportSize(viewport);
197
+ }
198
+ }
199
+ }
200
+ modules[section] = await page.evaluate(section => JSON.parse(JSON.stringify(window.__testaroQwRunners[section].getReport())), section);
201
+ }
202
+ if (Object.keys(rulePreventions).length) {
203
+ data.rulePreventions = rulePreventions;
204
+ }
205
+ // Add the module reports to the result.
206
+ result.nativeResult = { system: { url: page.url(), evaluation: 'in-page' }, modules };
207
+ const { standardResult } = result;
208
+ // For each section that was to be tested:
209
+ for (const section of Object.keys(sections)) {
210
+ const moduleReport = modules[section];
211
+ /*
212
+ A section that failed as a whole is recorded in rulePreventions; the act is prevented
213
+ only if no section ran.
214
+ */
215
+ if (!moduleReport) {
216
+ continue;
217
+ }
218
+ const { assertions } = moduleReport;
219
+ // If it contains assertions (test results):
220
+ if (assertions) {
205
221
  const ruleIDs = Object.keys(assertions);
206
222
  // For each rule:
207
223
  for (const ruleID of ruleIDs) {
208
- const ruleAssertions = assertions[ruleID];
209
- const {metadata} = ruleAssertions;
210
- // If there were any warnings or failures:
211
- if (metadata?.warning || metadata?.failed) {
212
- // Delete nonviolations from the results.
213
- ruleAssertions.results = ruleAssertions.results.filter(
214
- raResult => raResult.verdict !== 'passed'
215
- );
216
- // For each test result:
217
- for (const raResult of ruleAssertions.results) {
218
- const {elements, verdict} = raResult;
219
- // If any violations are reported:
220
- if (elements?.length) {
221
- // For each violating element:
222
- for (const element of elements) {
223
- // Limit the size of its reported excerpt.
224
- if (element.htmlCode?.length > 2000) {
225
- element.htmlCode = `${element.htmlCode.slice(0, 2000)} …`;
226
- }
227
- // If standard results are to be reported:
228
- if (standard) {
229
- const ordinalSeverity = ordinalSeverities[section][verdict];
230
- // Increment the applicable total.
231
- standardResult.totals[ordinalSeverity]++;
232
- // Initialize a standard instance.
233
- const what = `[${verdict}] ${raResult.description}`;
234
- const xPath = getAttributeXPath(element.htmlCode);
235
- const instance = {
236
- ruleID,
237
- what,
238
- ordinalSeverity: ordinalSeverities[section][verdict],
239
- count: 1,
240
- catalogIndex: getXPathCatalogIndex(report, xPath)
241
- };
242
- // Add the instance to the standard result.
243
- standardResult.instances.push(instance);
244
- }
245
- };
246
- }
247
- };
248
- }
249
- // Otherwise, i.e. if there were no warnings or failures:
250
- else {
251
- // Delete the rule.
252
- delete assertions[ruleID];
253
- }
254
- };
255
- }
256
- // Otherwise, i.e. if it contains no assertions:
257
- else {
258
- // Report this.
259
- data.prevented = true;
260
- data.error = 'No assertions';
261
- }
262
- }
263
- // Otherwise, i.e. if the section is missing:
264
- else {
265
- // Report this.
266
- data.prevented = true;
267
- data.error = `No ${section} section`;
224
+ const ruleAssertions = assertions[ruleID];
225
+ const { metadata } = ruleAssertions;
226
+ // If there were any warnings or failures:
227
+ if (metadata?.warning || metadata?.failed) {
228
+ // Delete nonviolations from the results.
229
+ ruleAssertions.results = ruleAssertions.results.filter(raResult => raResult.verdict !== 'passed');
230
+ // For each test result:
231
+ for (const raResult of ruleAssertions.results) {
232
+ const { elements, verdict } = raResult;
233
+ // If any violations are reported:
234
+ if (elements?.length) {
235
+ // For each violating element:
236
+ for (const element of elements) {
237
+ // Limit the size of its reported excerpt.
238
+ if (element.htmlCode?.length > 2000) {
239
+ element.htmlCode = `${element.htmlCode.slice(0, 2000)} …`;
240
+ }
241
+ // If standard results are to be reported:
242
+ if (standard) {
243
+ const what = `[${verdict}] ${raResult.description}`;
244
+ const xPath = (0, xPath_1.getAttributeXPath)(element.htmlCode);
245
+ // Add an instance to the standard result.
246
+ (0, standard_1.addInstance)(standardResult, {
247
+ ruleID,
248
+ what,
249
+ ordinalSeverity: ordinalSeverities[section][verdict],
250
+ outcome: verdict === 'warning' ? 'cantTell' : 'failed',
251
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, xPath)
252
+ });
253
+ }
254
+ }
255
+ ;
256
+ }
257
+ }
258
+ ;
259
+ }
260
+ // Otherwise, i.e. if there were no warnings or failures:
261
+ else {
262
+ // Delete the rule.
263
+ delete assertions[ruleID];
264
+ }
265
+ }
266
+ ;
268
267
  }
269
- }
270
268
  }
271
- }
272
- // Otherwise, i.e. if the report does not contain a modules property:
273
- else {
274
- // Report this.
275
- data.prevented = true;
276
- data.error = 'No modules';
277
- }
278
- }
279
- // Otherwise, i.e. if the report does not contain a copy of the DOM:
280
- else {
281
- // Report this.
282
- data.prevented = true;
283
- data.error = 'No DOM';
284
- }
285
- // Stop the QualWeb core engine.
286
- await qualWeb.stop();
287
- // Test whether the result is an object.
288
- try {
289
- JSON.stringify(result);
269
+ // If no section ran:
270
+ if (!Object.keys(modules).length) {
271
+ // Report this.
272
+ data.prevented = true;
273
+ data.error = `No qualWeb section ran (${JSON.stringify(rulePreventions).slice(0, 300)})`;
274
+ }
275
+ // Test whether the result is an object.
276
+ try {
277
+ JSON.stringify(result);
278
+ }
279
+ catch (error) {
280
+ data.prevented = true;
281
+ data.error = `QualWeb result cannot be made JSON (${error.message})`;
282
+ }
290
283
  }
291
- catch(error) {
292
- data.prevented = true;
293
- data.error = `QualWeb result cannot be made JSON (${error.message})`;
284
+ catch (error) {
285
+ data.prevented = true;
286
+ data.error = `QualWeb failed (${error.message})`;
294
287
  }
295
- }
296
- catch(error) {
297
- data.prevented = true;
298
- data.error = `QualWeb failed (${error.message})`;
299
- }
300
- return {
301
- data,
302
- result
303
- };
288
+ return {
289
+ data,
290
+ result
291
+ };
304
292
  };
293
+ exports.reporter = reporter;