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
@@ -0,0 +1,415 @@
1
+ /*
2
+ © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jeff Witt.
4
+ © 2025–2026 Jonathan Robert Pool.
5
+
6
+ Licensed under the MIT License. See LICENSE file at the project root or
7
+ https://opensource.org/license/mit/ for details.
8
+
9
+ SPDX-License-Identifier: MIT
10
+ */
11
+
12
+ // IMPORTS
13
+
14
+ import type {Page} from 'playwright';
15
+ import {getAttributeXPath, getXPathCatalogIndex} from '../procs/xPath';
16
+ import {getStandardResult, addInstance} from '../procs/standard';
17
+ import type {Act, Report, StandardInstance, StandardResult} from '../types';
18
+ /*
19
+ The @qualweb packages declare their types only in package-exports maps, which
20
+ this project's node10 module resolution cannot read, so the imports stay
21
+ requires and are untyped.
22
+ */
23
+ const {QualWeb} = require('@qualweb/core');
24
+ const {ACTRules} = require('@qualweb/act-rules');
25
+ const {WCAGTechniques} = require('@qualweb/wcag-techniques');
26
+ const {BestPractices} = require('@qualweb/best-practices');
27
+ const {PlaywrightDriver} = require('@qualweb/playwright-driver');
28
+ import {qualWebDefaults} from '../procs/config';
29
+
30
+ // TYPES
31
+
32
+ // The qualWeb-act properties this reporter consumes.
33
+ interface QualWebAct extends Act {
34
+ rules?: string[];
35
+ // Whether the tool's browser runs stealth evasions and blocks ads and trackers (both default
36
+ // to true, or to QUALWEB_STEALTH and QUALWEB_ADBLOCK when set).
37
+ stealth?: boolean;
38
+ adBlock?: boolean;
39
+ }
40
+ // The section names of a QualWeb report.
41
+ type QwSection = 'act-rules' | 'wcag-techniques' | 'best-practices';
42
+ // One element reported by a QualWeb assertion result.
43
+ interface QwElement {
44
+ htmlCode?: string;
45
+ }
46
+ // One result of a QualWeb rule assertion.
47
+ interface QwRaResult {
48
+ verdict: string;
49
+ description?: string;
50
+ elements?: QwElement[];
51
+ }
52
+ // The assertions for one QualWeb rule.
53
+ interface QwRuleAssertions {
54
+ metadata?: {
55
+ warning?: number;
56
+ failed?: number;
57
+ };
58
+ results: QwRaResult[];
59
+ }
60
+ // One module section of a QualWeb report.
61
+ interface QwModuleReport {
62
+ assertions?: Record<string, QwRuleAssertions>;
63
+ }
64
+ // The native result: the customHtml QualWeb report.
65
+ interface QwNativeResult {
66
+ system?: {
67
+ page?: {
68
+ dom?: unknown;
69
+ };
70
+ };
71
+ modules?: Record<string, QwModuleReport>;
72
+ }
73
+ // The options of a QualWeb evaluation.
74
+ interface QualWebOptions {
75
+ log: {console: boolean; file: boolean};
76
+ crawlOptions: {
77
+ maxDepth: number;
78
+ maxUrls: number;
79
+ timeout: number;
80
+ maxParallelCrawls: number;
81
+ logging: boolean;
82
+ };
83
+ execute: {counter: boolean; act?: boolean; wcag?: boolean; bp?: boolean};
84
+ modules: unknown[];
85
+ html?: string;
86
+ 'act-rules'?: {rules?: string[]; levels?: string[]; principles?: string[]};
87
+ 'wcag-techniques'?: {techniques?: string[]; levels?: string[]; principles?: string[]};
88
+ 'best-practices'?: {bestPractices?: string[]};
89
+ }
90
+
91
+ // CONSTANTS
92
+
93
+ /*
94
+ QualWeb core engine with Playwright as driver, one instance per combination of options,
95
+ created on first use. The driver's constructor options are {launcher, launchOptions,
96
+ browser}, so its former {adBlock, stealth} options were ignored; stealth is provided by
97
+ launching through playwright-extra with the stealth plugin, and ad and tracker blocking by
98
+ the Ghostery blocker enabled on each page before it loads (QualWeb's beforePageLoad hook).
99
+ The blocker's filter lists are cached in the temporary directory.
100
+ */
101
+ interface QualWebEngine {
102
+ start: (options: unknown) => Promise<void>;
103
+ evaluate: (options: unknown) => Promise<{customHtml: QwNativeResult}>;
104
+ stop: () => Promise<void>;
105
+ use: (plugin: unknown) => void;
106
+ }
107
+ const qualWebInstances: Record<string, QualWebEngine> = {};
108
+ const getQualWeb = (options: {stealth: boolean; adBlock: boolean}): QualWebEngine => {
109
+ const key = `${options.stealth}:${options.adBlock}`;
110
+ if (! qualWebInstances[key]) {
111
+ let launcher;
112
+ if (options.stealth) {
113
+ const {chromium} = require('playwright-extra');
114
+ chromium.use(require('puppeteer-extra-plugin-stealth')());
115
+ launcher = chromium;
116
+ }
117
+ const qualWeb: QualWebEngine = new QualWeb(undefined, new PlaywrightDriver(launcher ? {launcher} : {}));
118
+ if (options.adBlock) {
119
+ const {PlaywrightBlocker} = require('@ghostery/adblocker-playwright');
120
+ let blockerPromise: Promise<unknown> | null = null;
121
+ qualWeb.use({
122
+ async beforePageLoad(page: {nativePage: unknown}) {
123
+ blockerPromise ??= PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch, {
124
+ path: require('path').join(require('os').tmpdir(), 'qualweb-adblocker-engine.bin'),
125
+ read: require('fs').promises.readFile,
126
+ write: require('fs').promises.writeFile
127
+ }).catch((error: Error) => {
128
+ console.log(`qualWeb: ad blocker unavailable, continuing without it (${error.message})`);
129
+ return null;
130
+ });
131
+ const blocker = await blockerPromise as {enableBlockingInPage: (page: unknown) => Promise<void>} | null;
132
+ if (blocker) {
133
+ await blocker.enableBlockingInPage(page.nativePage);
134
+ }
135
+ }
136
+ });
137
+ }
138
+ qualWebInstances[key] = qualWeb;
139
+ }
140
+ return qualWebInstances[key];
141
+ };
142
+ const actRulesModule = new ACTRules({});
143
+ const wcagModule = new WCAGTechniques({});
144
+ const bpModule = new BestPractices({});
145
+ // Mapping of QualWeb module violation types to ordinal severities.
146
+ const ordinalSeverities: Record<QwSection, Record<string, StandardInstance['ordinalSeverity']>> = {
147
+ 'act-rules': {
148
+ 'warning': 1,
149
+ 'failed': 3
150
+ },
151
+ 'wcag-techniques': {
152
+ 'warning': 0,
153
+ 'failed': 2
154
+ },
155
+ 'best-practices': {
156
+ 'warning': 0,
157
+ 'failed': 1
158
+ }
159
+ }
160
+
161
+ /*
162
+ qualWeb
163
+ Implements the QualWeb ruleset for accessibility.
164
+ Compiled to qualWeb.js by tsc (issue #73); edit this file, not the emitted one.
165
+ */
166
+
167
+ // FUNCTIONS
168
+
169
+ // Conducts and reports the QualWeb tests.
170
+ export const reporter = async (page: Page, report: Report, actIndex: number, timeLimit: number) => {
171
+ const qualWebAct = report.acts[actIndex] as QualWebAct;
172
+ const qualWeb = getQualWeb({
173
+ stealth: qualWebAct.stealth ?? qualWebDefaults.stealth,
174
+ adBlock: qualWebAct.adBlock ?? qualWebDefaults.adBlock
175
+ });
176
+ const act = report.acts[actIndex] as QualWebAct;
177
+ const {rules} = act;
178
+ const clusterOptions = {
179
+ maxConcurrency: 1,
180
+ timeout: timeLimit * 1000,
181
+ monitor: false
182
+ };
183
+ // Initialize the act report.
184
+ const data: {prevented?: boolean; error?: string} = {};
185
+ const result: {nativeResult: QwNativeResult; standardResult: StandardResult} = {
186
+ nativeResult: {},
187
+ standardResult: {}
188
+ };
189
+ const standard = report.standard !== 'no';
190
+ // If standard results are to be reported:
191
+ if (standard) {
192
+ // Initialize the standard result.
193
+ result.standardResult = getStandardResult();
194
+ }
195
+ try {
196
+ // Start the QualWeb core engine, which launches a Playwright browser.
197
+ await qualWeb.start(clusterOptions);
198
+ }
199
+ // If the start fails:
200
+ catch(error) {
201
+ return {
202
+ data: {
203
+ prevented: true,
204
+ error: `Core engine start failed (${(error as Error).message})`
205
+ },
206
+ result
207
+ };
208
+ }
209
+ // Otherwise, i.e. if the start succeeds, specify the invariant test options.
210
+ const qualWebOptions: QualWebOptions = {
211
+ log: {
212
+ console: false,
213
+ file: false
214
+ },
215
+ crawlOptions: {
216
+ maxDepth: 0,
217
+ maxUrls: 1,
218
+ timeout: timeLimit * 1000,
219
+ maxParallelCrawls: 1,
220
+ logging: true
221
+ },
222
+ execute: {
223
+ counter: true
224
+ },
225
+ modules: []
226
+ };
227
+ try {
228
+ // Provide the page content, including the data-xpath attributes.
229
+ qualWebOptions.html = await page.content();
230
+ // Specify which rules to test for, adding a custom execute property for report processing.
231
+ const actSpec = rules ? rules.find(typeRules => typeRules.startsWith('act:')) : null;
232
+ const wcagSpec = rules ? rules.find(typeRules => typeRules.startsWith('wcag:')) : null;
233
+ const bestSpec = rules ? rules.find(typeRules => typeRules.startsWith('best:')) : null;
234
+ if (actSpec) {
235
+ if (actSpec === 'act:') {
236
+ qualWebOptions.execute.act = false;
237
+ }
238
+ else {
239
+ const actRules = actSpec.slice(4).split(',').map(num => `QW-ACT-R${num}`);
240
+ qualWebOptions['act-rules'] = {rules: actRules};
241
+ qualWebOptions.modules.push(actRulesModule);
242
+ qualWebOptions.execute.act = true;
243
+ }
244
+ }
245
+ else {
246
+ qualWebOptions['act-rules'] = {
247
+ levels: ['A', 'AA', 'AAA'],
248
+ principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
249
+ };
250
+ qualWebOptions.modules.push(actRulesModule);
251
+ qualWebOptions.execute.act = true;
252
+ }
253
+ if (wcagSpec) {
254
+ if (wcagSpec === 'wcag:') {
255
+ qualWebOptions.execute.wcag = false;
256
+ }
257
+ else {
258
+ const wcagTechniques = wcagSpec.slice(5).split(',').map(num => `QW-WCAG-T${num}`);
259
+ qualWebOptions['wcag-techniques'] = {techniques: wcagTechniques};
260
+ qualWebOptions.modules.push(wcagModule);
261
+ qualWebOptions.execute.wcag = true;
262
+ }
263
+ }
264
+ else {
265
+ qualWebOptions['wcag-techniques'] = {
266
+ levels: ['A', 'AA', 'AAA'],
267
+ principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
268
+ };
269
+ qualWebOptions.modules.push(wcagModule);
270
+ qualWebOptions.execute.wcag = true;
271
+ }
272
+ if (bestSpec) {
273
+ if (bestSpec === 'best:') {
274
+ qualWebOptions.execute.bp = false;
275
+ }
276
+ else {
277
+ const bestPractices = bestSpec.slice(5).split(',').map(num => `QW-BP${num}`);
278
+ qualWebOptions['best-practices'] = {bestPractices};
279
+ qualWebOptions.modules.push(bpModule);
280
+ qualWebOptions.execute.bp = true;
281
+ }
282
+ }
283
+ else {
284
+ qualWebOptions['best-practices'] = {};
285
+ qualWebOptions.modules.push(bpModule);
286
+ qualWebOptions.execute.bp = true;
287
+ }
288
+ let qwReport;
289
+ try {
290
+ // Get the report.
291
+ qwReport = await qualWeb.evaluate(qualWebOptions);
292
+ }
293
+ catch(error) {
294
+ return {
295
+ data: {
296
+ prevented: true,
297
+ error: `qualWeb evaluation failed (${(error as Error).message})`
298
+ },
299
+ result
300
+ };
301
+ }
302
+ // Add the report to the result.
303
+ result.nativeResult = qwReport.customHtml;
304
+ const {nativeResult, standardResult} = result;
305
+ // If the report contains, as it should, a copy of the DOM:
306
+ if (nativeResult?.system?.page?.dom) {
307
+ // Delete the copy for parsimony.
308
+ delete nativeResult.system.page.dom;
309
+ const {modules} = nativeResult;
310
+ // If the report contains, as it should, a modules property:
311
+ if (modules) {
312
+ // For each test section in it:
313
+ for (const section of ['act-rules', 'wcag-techniques', 'best-practices'] as QwSection[]) {
314
+ // If testing in the section was specified:
315
+ if (qualWebOptions[section]) {
316
+ // If the section exists:
317
+ if (modules[section]) {
318
+ const {assertions} = modules[section];
319
+ // If it contains assertions (test results):
320
+ if (assertions) {
321
+ const ruleIDs = Object.keys(assertions);
322
+ // For each rule:
323
+ for (const ruleID of ruleIDs) {
324
+ const ruleAssertions = assertions[ruleID];
325
+ const {metadata} = ruleAssertions;
326
+ // If there were any warnings or failures:
327
+ if (metadata?.warning || metadata?.failed) {
328
+ // Delete nonviolations from the results.
329
+ ruleAssertions.results = ruleAssertions.results.filter(
330
+ raResult => raResult.verdict !== 'passed'
331
+ );
332
+ // For each test result:
333
+ for (const raResult of ruleAssertions.results) {
334
+ const {elements, verdict} = raResult;
335
+ // If any violations are reported:
336
+ if (elements?.length) {
337
+ // For each violating element:
338
+ for (const element of elements) {
339
+ // Limit the size of its reported excerpt.
340
+ if ((element.htmlCode?.length as number) > 2000) {
341
+ element.htmlCode = `${element.htmlCode!.slice(0, 2000)} …`;
342
+ }
343
+ // If standard results are to be reported:
344
+ if (standard) {
345
+ const what = `[${verdict}] ${raResult.description}`;
346
+ const xPath = getAttributeXPath(element.htmlCode);
347
+ // Add an instance to the standard result.
348
+ addInstance(standardResult, {
349
+ ruleID,
350
+ what,
351
+ ordinalSeverity: ordinalSeverities[section][verdict],
352
+ outcome: verdict === 'warning' ? 'cantTell' : 'failed',
353
+ catalogIndex: getXPathCatalogIndex(report, xPath)
354
+ });
355
+ }
356
+ };
357
+ }
358
+ };
359
+ }
360
+ // Otherwise, i.e. if there were no warnings or failures:
361
+ else {
362
+ // Delete the rule.
363
+ delete assertions[ruleID];
364
+ }
365
+ };
366
+ }
367
+ // Otherwise, i.e. if it contains no assertions:
368
+ else {
369
+ // Report this.
370
+ data.prevented = true;
371
+ data.error = 'No assertions';
372
+ }
373
+ }
374
+ // Otherwise, i.e. if the section is missing:
375
+ else {
376
+ // Report this.
377
+ data.prevented = true;
378
+ data.error = `No ${section} section`;
379
+ }
380
+ }
381
+ }
382
+ }
383
+ // Otherwise, i.e. if the report does not contain a modules property:
384
+ else {
385
+ // Report this.
386
+ data.prevented = true;
387
+ data.error = 'No modules';
388
+ }
389
+ }
390
+ // Otherwise, i.e. if the report does not contain a copy of the DOM:
391
+ else {
392
+ // Report this.
393
+ data.prevented = true;
394
+ data.error = 'No DOM';
395
+ }
396
+ // Stop the QualWeb core engine.
397
+ await qualWeb.stop();
398
+ // Test whether the result is an object.
399
+ try {
400
+ JSON.stringify(result);
401
+ }
402
+ catch(error) {
403
+ data.prevented = true;
404
+ data.error = `QualWeb result cannot be made JSON (${(error as Error).message})`;
405
+ }
406
+ }
407
+ catch(error) {
408
+ data.prevented = true;
409
+ data.error = `QualWeb failed (${(error as Error).message})`;
410
+ }
411
+ return {
412
+ data,
413
+ result
414
+ };
415
+ };
@@ -0,0 +1,33 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Report, StandardResult } from '../types';
3
+ type SureSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
4
+ interface SureFinding {
5
+ ruleID: string;
6
+ what: string;
7
+ wcag: string;
8
+ severity: SureSeverity;
9
+ uncertainty: string;
10
+ needed: string;
11
+ html: string;
12
+ cssPath: string;
13
+ xPath: string;
14
+ }
15
+ interface SureNativeResult {
16
+ engine?: Record<string, unknown>;
17
+ violations?: SureFinding[];
18
+ incomplete?: SureFinding[];
19
+ passRuleCount?: number;
20
+ inapplicableRuleCount?: number;
21
+ manualRuleCount?: number;
22
+ manualOccurrenceCount?: number;
23
+ prevented?: boolean;
24
+ error?: string;
25
+ }
26
+ export declare const reporter: (page: Page, report: Report, actIndex: number) => Promise<{
27
+ data: Record<string, unknown>;
28
+ result: {
29
+ nativeResult: SureNativeResult;
30
+ standardResult: StandardResult;
31
+ };
32
+ }>;
33
+ export {};