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
@@ -0,0 +1,454 @@
1
+ /*
2
+ © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jeff Witt.
4
+ © 2025–2026 Jonathan Robert Pool.
5
+
6
+ Licensed under the MIT License. See LICENSE file at the project root or
7
+ https://opensource.org/license/mit/ for details.
8
+
9
+ SPDX-License-Identifier: MIT
10
+ */
11
+
12
+ // IMPORTS
13
+
14
+ import type {ElementHandle, Page} from 'playwright';
15
+ // Shared configuration for timeout multiplier.
16
+ import {applyMultiplier} from '../procs/config';
17
+ import {getXPathCatalogIndex} from '../procs/xPath';
18
+ import type {Report, StandardInstance} from '../types';
19
+
20
+ // TYPES
21
+
22
+ // A handle for a tab or tablist element.
23
+ type Handle = ElementHandle<SVGElement | HTMLElement>;
24
+ // Counts of navigations or elements, total and by correctness.
25
+ interface NavTally {
26
+ total: number;
27
+ correct: number;
28
+ incorrect: number;
29
+ }
30
+ // Data on one tested tab element.
31
+ interface TabItemData {
32
+ xPath?: string | null;
33
+ navigationErrors?: string[];
34
+ // Referenced in click-failure log messages but never assigned, so those logs print
35
+ // undefined; verbatim from the original.
36
+ text?: string;
37
+ }
38
+ // The module-level result data, populated once per reporter call.
39
+ interface TabNavData {
40
+ totals: {
41
+ navigations: {
42
+ all: NavTally;
43
+ specific: Record<string, NavTally>;
44
+ };
45
+ tabElements: NavTally;
46
+ tabLists: NavTally;
47
+ };
48
+ tabElements?: {
49
+ incorrect: TabItemData[];
50
+ correct: TabItemData[];
51
+ };
52
+ prevented?: boolean;
53
+ }
54
+
55
+ /*
56
+ tabNav
57
+ This test reports nonstandard keyboard navigation among tab elements in visible tab lists. Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel.
58
+ Compiled to tabNav.js by tsc (issue #73); edit this file, not the emitted one.
59
+ */
60
+
61
+ // CONSTANTS
62
+
63
+ // The cast types the object that reporter populates before the other functions read it.
64
+ const data = {} as TabNavData;
65
+
66
+ // FUNCTIONS
67
+
68
+ // Returns the index of the focused tab in an array of tabs.
69
+ const focusedTab = async (tabs: Handle[], page: Page) => await page.evaluate(tabs => {
70
+ const focus = document.activeElement;
71
+ return tabs.indexOf(focus as SVGElement | HTMLElement);
72
+ }, tabs)
73
+ .catch(error => {
74
+ console.log(`ERROR: could not find focused tab (${error.message})`);
75
+ return -1;
76
+ });
77
+ // Tests a navigation on a focused tab element.
78
+ const testKey = async (
79
+ tabs: Handle[],
80
+ tabElement: Handle,
81
+ keyName: string,
82
+ keyProp: string,
83
+ goodIndex: number | undefined,
84
+ elementIsCorrect: boolean,
85
+ itemData: TabItemData,
86
+ withItems: boolean,
87
+ page: Page
88
+ ) => {
89
+ let pressed = true;
90
+ // Click the tab element, to make the focus on it effective.
91
+ await tabElement.click({
92
+ timeout: applyMultiplier(500)
93
+ })
94
+ .catch(async error => {
95
+ console.log(
96
+ `ERROR clicking tab element ${itemData.text} (${error.message.replace(/\n.+/s, '')})`
97
+ );
98
+ await tabElement.click({
99
+ force: true
100
+ });
101
+ })
102
+ .catch(error => {
103
+ console.log(
104
+ `ERROR force-clicking tab element ${itemData.text} (${error.message.replace(/\n.+/s, '')})`
105
+ );
106
+ pressed = false;
107
+ });
108
+ // Increment the counts of navigations and key navigations.
109
+ data.totals.navigations.all.total++;
110
+ data.totals.navigations.specific[keyProp].total++;
111
+ const {navigationErrors} = itemData;
112
+ // If the click succeeded:
113
+ if (pressed) {
114
+ // Refocus the tab element and press the specified key (page.keyboard.press may fail).
115
+ await tabElement.press(keyName, {
116
+ timeout: applyMultiplier(1000)
117
+ })
118
+ .catch(error => {
119
+ console.log(`ERROR: could not press ${keyName} (${error.message})`);
120
+ pressed = false;
121
+ });
122
+ // If the refocus and keypress succeeded:
123
+ if (pressed) {
124
+ // Identify which tab element is now focused, if any.
125
+ const focusIndex = await focusedTab(tabs, page);
126
+ // If the focus is correct:
127
+ if (focusIndex === goodIndex) {
128
+ // Increment the counts of correct navigations and correct key navigations.
129
+ data.totals.navigations.all.correct++;
130
+ data.totals.navigations.specific[keyProp].correct++;
131
+ }
132
+ // Otherwise, i.e. if the focus is incorrect:
133
+ else {
134
+ // Increment the counts of incorrect navigations and incorrect key navigations.
135
+ data.totals.navigations.all.incorrect++;
136
+ data.totals.navigations.specific[keyProp].incorrect++;
137
+ // Update the element status to incorrect.
138
+ elementIsCorrect = false;
139
+ // If itemization is required:
140
+ if (withItems) {
141
+ // Update the element report.
142
+ navigationErrors!.push(keyName);
143
+ }
144
+ }
145
+ return elementIsCorrect;
146
+ }
147
+ // Otherwise, i.e. if the refocus or keypress failed:
148
+ else {
149
+ // Increment the counts of incorrect navigations and incorrect key navigations.
150
+ data.totals.navigations.all.incorrect++;
151
+ data.totals.navigations.specific[keyProp].incorrect++;
152
+ // If itemization is required and a focus failure has not yet been reported:
153
+ if (withItems && ! navigationErrors!.includes('focus')) {
154
+ // Update the element report.
155
+ navigationErrors!.push('focus');
156
+ }
157
+ return false;
158
+ }
159
+ }
160
+ // Otherwise, i.e. if the click failed:
161
+ else {
162
+ // Increment the counts of incorrect navigations and incorrect key navigations.
163
+ data.totals.navigations.all.incorrect++;
164
+ data.totals.navigations.specific[keyProp].incorrect++;
165
+ // If itemization is required and a click failure has not yet been reported:
166
+ if (withItems && ! navigationErrors!.includes('click')) {
167
+ // Update the element report.
168
+ navigationErrors!.push('click');
169
+ }
170
+ return false;
171
+ }
172
+ };
173
+ // Returns the index to which an arrow key should move the focus.
174
+ const arrowTarget = (
175
+ startIndex: number, tabCount: number, orientation: string, direction: string
176
+ ) => {
177
+ if (orientation === 'horizontal') {
178
+ if (['up', 'down'].includes(direction)) {
179
+ return startIndex;
180
+ }
181
+ else if (direction === 'left') {
182
+ return startIndex ? startIndex - 1 : tabCount - 1;
183
+ }
184
+ else if (direction === 'right') {
185
+ return startIndex === tabCount - 1 ? 0 : startIndex + 1;
186
+ }
187
+ }
188
+ else if (orientation === 'vertical') {
189
+ if (['left', 'right'].includes(direction)) {
190
+ return startIndex;
191
+ }
192
+ else if (direction === 'up') {
193
+ return startIndex ? startIndex - 1 : tabCount - 1;
194
+ }
195
+ else if (direction === 'down') {
196
+ return startIndex === tabCount - 1 ? 0 : startIndex + 1;
197
+ }
198
+ }
199
+ };
200
+ /*
201
+ Recursively tests tablist tab elements (per
202
+ https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel)
203
+ */
204
+ const testTabs = async (
205
+ tabs: Handle[],
206
+ index: number,
207
+ listOrientation: string,
208
+ listIsCorrect: boolean,
209
+ withItems: boolean,
210
+ page: Page
211
+ ): Promise<boolean> => {
212
+ const tabCount = tabs.length;
213
+ // If any tab elements remain to be tested:
214
+ if (index < tabCount) {
215
+ // Increment the reported count of tab elements.
216
+ data.totals.tabElements.total++;
217
+ // Identify the tab element to be tested.
218
+ const currentTab = tabs[index];
219
+ // Initialize it as correct.
220
+ let isCorrect = true;
221
+ const itemData: TabItemData = {};
222
+ // If itemization is required:
223
+ if (withItems) {
224
+ // Initialize data on the element.
225
+ itemData.xPath = await page.evaluate(element => window.getXPath(element), currentTab);
226
+ itemData.navigationErrors = [];
227
+ }
228
+ // Test the element with each navigation key.
229
+ isCorrect = await testKey(
230
+ tabs,
231
+ currentTab,
232
+ 'Tab',
233
+ 'tab',
234
+ -1,
235
+ isCorrect,
236
+ itemData,
237
+ withItems,
238
+ page
239
+ );
240
+ isCorrect = await testKey(
241
+ tabs,
242
+ currentTab,
243
+ 'ArrowLeft',
244
+ 'left',
245
+ arrowTarget(index, tabCount, listOrientation, 'left'),
246
+ isCorrect,
247
+ itemData,
248
+ withItems,
249
+ page
250
+ );
251
+ isCorrect = await testKey(
252
+ tabs,
253
+ currentTab,
254
+ 'ArrowRight',
255
+ 'right',
256
+ arrowTarget(index, tabCount, listOrientation, 'right'),
257
+ isCorrect,
258
+ itemData,
259
+ withItems,
260
+ page
261
+ );
262
+ isCorrect = await testKey(
263
+ tabs,
264
+ currentTab,
265
+ 'ArrowUp',
266
+ 'up',
267
+ arrowTarget(index, tabCount, listOrientation, 'up'),
268
+ isCorrect,
269
+ itemData,
270
+ withItems,
271
+ page
272
+ );
273
+ isCorrect = await testKey(
274
+ tabs,
275
+ currentTab,
276
+ 'ArrowDown',
277
+ 'down',
278
+ arrowTarget(index, tabCount, listOrientation, 'down'),
279
+ isCorrect,
280
+ itemData,
281
+ withItems,
282
+ page
283
+ );
284
+ isCorrect = await testKey(
285
+ tabs,
286
+ currentTab,
287
+ 'Home',
288
+ 'home',
289
+ 0,
290
+ isCorrect,
291
+ itemData,
292
+ withItems,
293
+ page
294
+ );
295
+ isCorrect = await testKey(
296
+ tabs, currentTab, 'End', 'end', tabCount - 1, isCorrect, itemData, withItems, page
297
+ );
298
+ // Update the tablist status (Node 14 does not support the ES 2021 &&= operator).
299
+ listIsCorrect = listIsCorrect && isCorrect;
300
+ // Increment the data.
301
+ data.totals.tabElements[isCorrect ? 'correct' : 'incorrect']++;
302
+ if (withItems) {
303
+ data.tabElements![isCorrect ? 'correct' : 'incorrect'].push(itemData);
304
+ }
305
+ // Process the next tab element.
306
+ return await testTabs(tabs, index + 1, listOrientation, listIsCorrect, withItems, page);
307
+ }
308
+ // Otherwise, i.e. if all tab elements have been tested:
309
+ else {
310
+ // Return whether the tablist is correct.
311
+ return listIsCorrect;
312
+ }
313
+ };
314
+ // Recursively tests tablists.
315
+ const testTabLists = async (tabLists: Handle[], withItems: boolean, page: Page): Promise<void> => {
316
+ // If any tablists remain to be tested:
317
+ if (tabLists.length) {
318
+ const firstTabList = tabLists[0];
319
+ let orientation = await firstTabList.getAttribute('aria-orientation')
320
+ .catch(error=> {
321
+ console.log(`ERROR: could not get tab-list orientation (${error.message})`);
322
+ return 'ERROR';
323
+ });
324
+ if (! orientation) {
325
+ orientation = 'horizontal';
326
+ }
327
+ if (orientation === 'ERROR') {
328
+ data.prevented = true;
329
+ }
330
+ else {
331
+ const tabs = await firstTabList.$$('[role=tab]');
332
+ // If the tablist contains at least 2 tab elements:
333
+ if (tabs.length > 1) {
334
+ // Test them.
335
+ const isCorrect = await testTabs(tabs, 0, orientation, true, withItems, page);
336
+ // Increment the data.
337
+ data.totals.tabLists.total++;
338
+ data.totals.tabLists[isCorrect ? 'correct' : 'incorrect']++;
339
+ // Process the remaining tablists.
340
+ await testTabLists(tabLists.slice(1), withItems, page);
341
+ }
342
+ }
343
+ }
344
+ };
345
+ // Runs the test and returns the result.
346
+ export const reporter = async (page: Page, report: Report, _: unknown, withItems: boolean) => {
347
+ // Initialize the results.
348
+ data.totals = {
349
+ navigations: {
350
+ all: {
351
+ total: 0,
352
+ correct: 0,
353
+ incorrect: 0
354
+ },
355
+ specific: {
356
+ tab: {
357
+ total: 0,
358
+ correct: 0,
359
+ incorrect: 0
360
+ },
361
+ left: {
362
+ total: 0,
363
+ correct: 0,
364
+ incorrect: 0
365
+ },
366
+ right: {
367
+ total: 0,
368
+ correct: 0,
369
+ incorrect: 0
370
+ },
371
+ up: {
372
+ total: 0,
373
+ correct: 0,
374
+ incorrect: 0
375
+ },
376
+ down: {
377
+ total: 0,
378
+ correct: 0,
379
+ incorrect: 0
380
+ },
381
+ home: {
382
+ total: 0,
383
+ correct: 0,
384
+ incorrect: 0
385
+ },
386
+ end: {
387
+ total: 0,
388
+ correct: 0,
389
+ incorrect: 0
390
+ }
391
+ }
392
+ },
393
+ tabElements: {
394
+ total: 0,
395
+ correct: 0,
396
+ incorrect: 0
397
+ },
398
+ tabLists: {
399
+ total: 0,
400
+ correct: 0,
401
+ incorrect: 0
402
+ }
403
+ };
404
+ if (withItems) {
405
+ data.tabElements = {
406
+ incorrect: [],
407
+ correct: []
408
+ };
409
+ }
410
+ // Get an array of element handles for the visible tablists.
411
+ const tabLists = await page.$$('[role=tablist]:visible');
412
+ if (tabLists.length) {
413
+ await testTabLists(tabLists, withItems, page);
414
+ }
415
+ // Get the totals of navigation errors, bad tabs, and bad tab lists.
416
+ const totals = data.totals ? [
417
+ data.totals.navigations.all.incorrect,
418
+ data.totals.tabElements.incorrect,
419
+ data.totals.tabLists.incorrect,
420
+ 0
421
+ ] : [];
422
+ // Initialize the standard instances.
423
+ const standardInstances: StandardInstance[] = [];
424
+ // If itemization is required:
425
+ if (withItems) {
426
+ // For each bad tab:
427
+ data.tabElements!.incorrect.forEach(item => {
428
+ // Add an instance to the standard instances.
429
+ standardInstances.push({
430
+ ruleID: 'tabNav',
431
+ what: `Tab responds nonstandardly to ${item.navigationErrors!.join(', ')}`,
432
+ ordinalSeverity: 1,
433
+ count: 1,
434
+ catalogIndex: getXPathCatalogIndex(report, item.xPath as string)
435
+ });
436
+ });
437
+ }
438
+ // Otherwise, if navigation is not required and any navigations were bad:
439
+ else if (data.totals.navigations.all.incorrect) {
440
+ // Add a summary instance to the standard instances.
441
+ standardInstances.push({
442
+ ruleID: 'tabNav',
443
+ what: 'Tab lists have nonstandard navigation',
444
+ ordinalSeverity: 1,
445
+ count: data.totals.navigations.all.incorrect
446
+ });
447
+ }
448
+ // Return the result.
449
+ return {
450
+ data,
451
+ totals,
452
+ standardInstances
453
+ };
454
+ };
@@ -0,0 +1,9 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Report, StandardInstance } from '../types';
3
+ export declare const reporter: (page: Page, report: Report, _: unknown, withItems: boolean) => Promise<{
4
+ data: {};
5
+ totals: number[];
6
+ standardInstances: (StandardInstance & {
7
+ xPath?: string;
8
+ })[];
9
+ }>;