testaro 78.0.6 → 78.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/.gitattributes +94 -0
  2. package/.github/workflows/ci.yml +58 -0
  3. package/.github/workflows/publish.yml +49 -0
  4. package/.github/workflows/typescript.yml +35 -0
  5. package/AGENTS.md +2 -2
  6. package/CLAUDE.md +25 -19
  7. package/CONTAINERS.md +10 -6
  8. package/CONTRIBUTING.md +29 -3
  9. package/Dockerfile +1 -1
  10. package/README.md +75 -20
  11. package/UPGRADES.md +4 -0
  12. package/actSpecs-doc.md +13 -1
  13. package/actSpecs.js +29 -5
  14. package/call.js +6 -5
  15. package/docker-compose.yml +8 -2
  16. package/docs/checkpoint-scanning.md +199 -0
  17. package/docs/standard-result-outcome.md +165 -0
  18. package/env.example +44 -10
  19. package/eslint.config.mjs +101 -0
  20. package/netWatch.js +373 -222
  21. package/package.json +46 -31
  22. package/pour/README.md +47 -0
  23. package/pour/pour.min.js +8 -0
  24. package/procs/actDo.js +808 -0
  25. package/procs/catalog.d.ts +17 -0
  26. package/procs/catalog.js +313 -221
  27. package/procs/catalog.ts +398 -0
  28. package/procs/checkpoint.js +110 -0
  29. package/procs/config.d.ts +35 -0
  30. package/procs/config.js +63 -0
  31. package/procs/dateTime.js +2 -1
  32. package/procs/doActs.js +442 -849
  33. package/procs/doTestAct.js +27 -121
  34. package/procs/flow.js +221 -0
  35. package/procs/generateRuleRegistry.js +94 -0
  36. package/procs/getSource.d.ts +26 -0
  37. package/procs/getSource.js +1 -0
  38. package/procs/job.js +116 -1
  39. package/procs/launch.d.ts +40 -0
  40. package/procs/launch.js +239 -108
  41. package/procs/nu.d.ts +48 -0
  42. package/procs/nu.js +1 -0
  43. package/procs/scope.js +196 -0
  44. package/procs/shoot.d.ts +33 -0
  45. package/procs/shoot.js +3 -1
  46. package/procs/standard.d.ts +18 -0
  47. package/procs/standard.js +83 -0
  48. package/procs/standard.ts +126 -0
  49. package/procs/testAct.js +145 -0
  50. package/procs/testaro.d.ts +20 -0
  51. package/procs/testaro.js +237 -189
  52. package/procs/testaro.ts +321 -0
  53. package/procs/userPath.js +126 -0
  54. package/procs/xPath.d.ts +4 -0
  55. package/procs/xPath.js +82 -64
  56. package/procs/xPath.ts +106 -0
  57. package/procs/xPathScript.d.ts +6 -0
  58. package/procs/xPathScript.js +70 -0
  59. package/run.js +3 -2
  60. package/surea11y/README.md +44 -0
  61. package/surea11y/surea11y.browser.js +14 -0
  62. package/testaro/adbID.d.ts +3 -0
  63. package/testaro/adbID.js +39 -41
  64. package/testaro/adbID.ts +66 -0
  65. package/testaro/allCapStyle.d.ts +3 -0
  66. package/testaro/allCapStyle.js +32 -34
  67. package/testaro/allCapStyle.ts +55 -0
  68. package/testaro/allCaps.d.ts +16 -0
  69. package/testaro/allCaps.js +179 -151
  70. package/testaro/allCaps.ts +228 -0
  71. package/testaro/allHidden.d.ts +13 -0
  72. package/testaro/allHidden.js +30 -30
  73. package/testaro/allHidden.ts +50 -0
  74. package/testaro/allSlanted.d.ts +3 -0
  75. package/testaro/allSlanted.js +31 -33
  76. package/testaro/allSlanted.ts +54 -0
  77. package/testaro/altScheme.d.ts +3 -0
  78. package/testaro/altScheme.js +28 -30
  79. package/testaro/altScheme.ts +50 -0
  80. package/testaro/attVal.d.ts +3 -0
  81. package/testaro/attVal.js +21 -21
  82. package/testaro/attVal.ts +52 -0
  83. package/testaro/autocomplete.d.ts +3 -0
  84. package/testaro/autocomplete.js +60 -75
  85. package/testaro/autocomplete.ts +101 -0
  86. package/testaro/bulk.d.ts +13 -0
  87. package/testaro/bulk.js +33 -33
  88. package/testaro/bulk.ts +55 -0
  89. package/testaro/buttonMenu.d.ts +9 -0
  90. package/testaro/buttonMenu.js +318 -319
  91. package/testaro/buttonMenu.ts +391 -0
  92. package/testaro/captionLoc.d.ts +3 -0
  93. package/testaro/captionLoc.js +18 -20
  94. package/testaro/captionLoc.ts +40 -0
  95. package/testaro/datalistRef.d.ts +3 -0
  96. package/testaro/datalistRef.js +34 -36
  97. package/testaro/datalistRef.ts +55 -0
  98. package/testaro/distortion.d.ts +3 -0
  99. package/testaro/distortion.js +58 -26
  100. package/testaro/distortion.ts +81 -0
  101. package/testaro/docType.d.ts +15 -0
  102. package/testaro/docType.js +26 -25
  103. package/testaro/docType.ts +45 -0
  104. package/testaro/dupAtt.d.ts +16 -0
  105. package/testaro/dupAtt.js +114 -104
  106. package/testaro/dupAtt.ts +144 -0
  107. package/testaro/elements.d.ts +6 -0
  108. package/testaro/elements.js +153 -153
  109. package/testaro/elements.ts +215 -0
  110. package/testaro/embAc.d.ts +3 -0
  111. package/testaro/embAc.js +20 -20
  112. package/testaro/embAc.ts +40 -0
  113. package/testaro/focAll.d.ts +13 -0
  114. package/testaro/focAll.js +186 -191
  115. package/testaro/focAll.ts +217 -0
  116. package/testaro/focAndOp.d.ts +3 -0
  117. package/testaro/focAndOp.js +101 -104
  118. package/testaro/focAndOp.ts +128 -0
  119. package/testaro/focInd.d.ts +3 -0
  120. package/testaro/focInd.js +65 -66
  121. package/testaro/focInd.ts +96 -0
  122. package/testaro/focVis.d.ts +3 -0
  123. package/testaro/focVis.js +29 -30
  124. package/testaro/focVis.ts +52 -0
  125. package/testaro/headEl.d.ts +10 -0
  126. package/testaro/headEl.js +62 -62
  127. package/testaro/headEl.ts +82 -0
  128. package/testaro/headingAmb.d.ts +3 -0
  129. package/testaro/headingAmb.js +55 -63
  130. package/testaro/headingAmb.ts +72 -0
  131. package/testaro/hovInd.d.ts +12 -0
  132. package/testaro/hovInd.js +164 -130
  133. package/testaro/hovInd.ts +199 -0
  134. package/testaro/hover.d.ts +3 -0
  135. package/testaro/hover.js +156 -126
  136. package/testaro/hover.ts +154 -0
  137. package/testaro/hr.d.ts +3 -0
  138. package/testaro/hr.js +15 -17
  139. package/testaro/hr.ts +36 -0
  140. package/testaro/imageLink.d.ts +3 -0
  141. package/testaro/imageLink.js +18 -20
  142. package/testaro/imageLink.ts +42 -0
  143. package/testaro/labClash.d.ts +3 -0
  144. package/testaro/labClash.js +32 -33
  145. package/testaro/labClash.ts +54 -0
  146. package/testaro/legendLoc.d.ts +3 -0
  147. package/testaro/legendLoc.js +18 -20
  148. package/testaro/legendLoc.ts +42 -0
  149. package/testaro/lineHeight.d.ts +3 -0
  150. package/testaro/lineHeight.js +44 -48
  151. package/testaro/lineHeight.ts +70 -0
  152. package/testaro/linkAmb.d.ts +7 -0
  153. package/testaro/linkAmb.js +80 -80
  154. package/testaro/linkAmb.ts +105 -0
  155. package/testaro/linkExt.d.ts +3 -0
  156. package/testaro/linkExt.js +14 -16
  157. package/testaro/linkExt.ts +35 -0
  158. package/testaro/linkOldAtt.d.ts +3 -0
  159. package/testaro/linkOldAtt.js +26 -28
  160. package/testaro/linkOldAtt.ts +48 -0
  161. package/testaro/linkTo.d.ts +3 -0
  162. package/testaro/linkTo.js +23 -22
  163. package/testaro/linkTo.ts +43 -0
  164. package/testaro/linkUl.d.ts +3 -0
  165. package/testaro/linkUl.js +31 -34
  166. package/testaro/linkUl.ts +54 -0
  167. package/testaro/miniText.d.ts +3 -0
  168. package/testaro/miniText.js +42 -44
  169. package/testaro/miniText.ts +68 -0
  170. package/testaro/motion.d.ts +10 -0
  171. package/testaro/motion.js +93 -96
  172. package/testaro/motion.ts +125 -0
  173. package/testaro/nonTable.d.ts +3 -0
  174. package/testaro/nonTable.js +40 -45
  175. package/testaro/nonTable.ts +66 -0
  176. package/testaro/optRoleSel.d.ts +3 -0
  177. package/testaro/optRoleSel.js +17 -19
  178. package/testaro/optRoleSel.ts +41 -0
  179. package/testaro/phOnly.d.ts +3 -0
  180. package/testaro/phOnly.js +19 -21
  181. package/testaro/phOnly.ts +43 -0
  182. package/testaro/pseudoP.d.ts +3 -0
  183. package/testaro/pseudoP.js +37 -38
  184. package/testaro/pseudoP.ts +59 -0
  185. package/testaro/radioSet.d.ts +3 -0
  186. package/testaro/radioSet.js +58 -59
  187. package/testaro/radioSet.ts +79 -0
  188. package/testaro/registry.d.ts +63 -0
  189. package/testaro/registry.js +67 -0
  190. package/testaro/registry.ts +141 -0
  191. package/testaro/role.d.ts +3 -0
  192. package/testaro/role.js +28 -29
  193. package/testaro/role.ts +53 -0
  194. package/testaro/secHeading.d.ts +3 -0
  195. package/testaro/secHeading.js +31 -33
  196. package/testaro/secHeading.ts +53 -0
  197. package/testaro/styleDiff.d.ts +25 -0
  198. package/testaro/styleDiff.js +249 -252
  199. package/testaro/styleDiff.ts +303 -0
  200. package/testaro/tabNav.d.ts +33 -0
  201. package/testaro/tabNav.js +273 -342
  202. package/testaro/tabNav.ts +454 -0
  203. package/testaro/targetsNear.d.ts +9 -0
  204. package/testaro/targetsNear.js +131 -132
  205. package/testaro/targetsNear.ts +160 -0
  206. package/testaro/textNodes.d.ts +6 -0
  207. package/testaro/textNodes.js +139 -135
  208. package/testaro/textNodes.ts +185 -0
  209. package/testaro/textSem.d.ts +3 -0
  210. package/testaro/textSem.js +26 -28
  211. package/testaro/textSem.ts +47 -0
  212. package/testaro/title.d.ts +9 -0
  213. package/testaro/title.js +16 -13
  214. package/testaro/title.ts +31 -0
  215. package/testaro/titledEl.d.ts +3 -0
  216. package/testaro/titledEl.js +16 -18
  217. package/testaro/titledEl.ts +38 -0
  218. package/testaro/zIndex.d.ts +3 -0
  219. package/testaro/zIndex.js +20 -22
  220. package/testaro/zIndex.ts +42 -0
  221. package/tests/alfa.d.ts +45 -0
  222. package/tests/alfa.js +139 -141
  223. package/tests/alfa.ts +214 -0
  224. package/tests/aslint.d.ts +33 -0
  225. package/tests/aslint.js +273 -249
  226. package/tests/aslint.ts +301 -0
  227. package/tests/axe.d.ts +27 -0
  228. package/tests/axe.js +200 -200
  229. package/tests/axe.ts +277 -0
  230. package/tests/ed11y.d.ts +28 -0
  231. package/tests/ed11y.js +142 -99
  232. package/tests/ed11y.ts +178 -0
  233. package/tests/htmlcs.d.ts +21 -0
  234. package/tests/htmlcs.js +175 -140
  235. package/tests/htmlcs.ts +181 -0
  236. package/tests/ibm.d.ts +52 -0
  237. package/tests/ibm.js +165 -166
  238. package/tests/ibm.ts +251 -0
  239. package/tests/nuVal.d.ts +13 -0
  240. package/tests/nuVal.js +107 -112
  241. package/tests/nuVal.ts +145 -0
  242. package/tests/nuVnu.d.ts +15 -0
  243. package/tests/nuVnu.js +142 -111
  244. package/tests/nuVnu.ts +144 -0
  245. package/tests/pour.d.ts +31 -0
  246. package/tests/pour.js +242 -0
  247. package/tests/pour.ts +273 -0
  248. package/tests/qualWeb.d.ts +39 -0
  249. package/tests/qualWeb.js +303 -272
  250. package/tests/qualWeb.ts +415 -0
  251. package/tests/surea11y.d.ts +33 -0
  252. package/tests/surea11y.js +288 -0
  253. package/tests/surea11y.ts +334 -0
  254. package/tests/testaro.d.ts +25 -0
  255. package/tests/testaro.js +746 -652
  256. package/tests/testaro.ts +862 -0
  257. package/tests/wave.d.ts +46 -0
  258. package/tests/wave.js +167 -177
  259. package/tests/wave.ts +252 -0
  260. package/tsconfig.json +17 -0
  261. package/types.d.ts +243 -0
  262. package/types.js +10 -0
  263. package/types.ts +376 -0
  264. package/validation/act/README.md +46 -0
  265. package/validation/act/capture.js +424 -0
  266. package/validation/act/chromium-issue-draft.md +66 -0
  267. package/validation/act/fp-triage-2026-08-22.md +86 -0
  268. package/validation/act/isolation-notes.md +159 -0
  269. package/validation/act/playwright-issue-draft.md +73 -0
  270. package/validation/act/propose-mappings.js +0 -0
  271. package/validation/act/repro-cdp-raw.js +105 -0
  272. package/validation/act/repro-metarefresh.js +65 -0
  273. package/validation/act/score.js +184 -0
  274. package/validation/act/stage3a-stress-report.md +69 -0
  275. package/validation/act/stage3b-mapping-proposals.md +94 -0
  276. package/validation/act/stage3b-triage-draft.md +138 -0
  277. package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
  278. package/validation/executors/netWatch.js +180 -90
  279. package/validation/executors/test.js +17 -2
  280. package/validation/executors/tests.js +118 -10
  281. package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
  282. package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
  283. package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
  284. package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
  285. package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
  286. package/validation/knownFailures.json +1 -0
  287. package/validation/tests/jobProperties/adbID.json +27 -2
  288. package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
  289. package/validation/tests/jobProperties/allCaps.json +25 -0
  290. package/validation/tests/jobProperties/allHidden.json +133 -13
  291. package/validation/tests/jobProperties/allSlanted.json +3 -3
  292. package/validation/tests/jobProperties/altScheme.json +23 -8
  293. package/validation/tests/jobProperties/attVal.json +57 -57
  294. package/validation/tests/jobProperties/autocomplete.json +11 -1
  295. package/validation/tests/jobProperties/bulk.json +6 -1
  296. package/validation/tests/jobProperties/buttonMenu.json +63 -42
  297. package/validation/tests/jobProperties/captionLoc.json +1 -6
  298. package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
  299. package/validation/tests/jobProperties/checkpoint-page.json +397 -0
  300. package/validation/tests/jobProperties/checkpoint.json +395 -0
  301. package/validation/tests/jobProperties/datalistRef.json +20 -5
  302. package/validation/tests/jobProperties/distortion.json +28 -3
  303. package/validation/tests/jobProperties/docType.json +2 -2
  304. package/validation/tests/jobProperties/dupAtt.json +48 -33
  305. package/validation/tests/jobProperties/elements.json +28 -28
  306. package/validation/tests/jobProperties/embAc.json +36 -31
  307. package/validation/tests/jobProperties/focAndOp.json +284 -0
  308. package/validation/tests/jobProperties/focInd.json +34 -34
  309. package/validation/tests/jobProperties/focVis.json +1 -1
  310. package/validation/tests/jobProperties/hover.json +39 -37
  311. package/validation/tests/jobProperties/hr.json +13 -3
  312. package/validation/tests/jobProperties/imageLink.json +0 -5
  313. package/validation/tests/jobProperties/labClash.json +68 -33
  314. package/validation/tests/jobProperties/legendLoc.json +1 -6
  315. package/validation/tests/jobProperties/lineHeight.json +2 -2
  316. package/validation/tests/jobProperties/linkAmb.json +13 -18
  317. package/validation/tests/jobProperties/linkExt.json +1 -1
  318. package/validation/tests/jobProperties/linkOldAtt.json +12 -2
  319. package/validation/tests/jobProperties/linkTo.json +1 -1
  320. package/validation/tests/jobProperties/linkUl.json +65 -65
  321. package/validation/tests/jobProperties/miniText.json +7 -2
  322. package/validation/tests/jobProperties/motion.json +4 -50
  323. package/validation/tests/jobProperties/nonTable.json +48 -3
  324. package/validation/tests/jobProperties/optRoleSel.json +12 -2
  325. package/validation/tests/jobProperties/phOnly.json +6 -16
  326. package/validation/tests/jobProperties/pseudoP.json +17 -2
  327. package/validation/tests/jobProperties/radioSet.json +30 -30
  328. package/validation/tests/jobProperties/role.json +25 -5
  329. package/validation/tests/jobProperties/secHeading.json +26 -21
  330. package/validation/tests/jobProperties/styleDiff.json +35 -5
  331. package/validation/tests/jobProperties/tabNav.json +3 -1
  332. package/validation/tests/jobProperties/targetsNear.json +237 -0
  333. package/validation/tests/jobProperties/textNodes.json +29 -29
  334. package/validation/tests/jobProperties/textSem.json +26 -1
  335. package/validation/tests/jobProperties/title.json +12 -2
  336. package/validation/tests/jobProperties/titledEl.json +39 -9
  337. package/validation/tests/jobProperties/userPath.json +317 -0
  338. package/validation/tests/jobProperties/zIndex.json +40 -35
  339. package/validation/tests/targets/allCapStyle/index.html +26 -0
  340. package/validation/tests/targets/checkpoint/index.html +38 -0
  341. package/validation/tests/targets/datalistRef/index.html +1 -1
  342. package/validation/tests/targets/focAndOp/bad.html +29 -0
  343. package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
  344. package/validation/tests/targets/focInd/bad.html +2 -1
  345. package/validation/tests/targets/headEl/index.html +10 -1
  346. package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
  347. package/validation/tests/targets/userPath/index.html +42 -0
  348. package/validation/validateTest.js +62 -10
  349. package/.claude/settings.local.json +0 -11
  350. package/.eslintrc.json +0 -41
  351. package/htmlcs/.eslintrc.json +0 -67
  352. package/memory/MEMORY.md +0 -3
  353. package/memory/project_validation_pause.md +0 -10
  354. package/validation/tests/jobProperties/linkTitle.json +0 -127
  355. package/validation/tests/jobProperties/opFoc.json +0 -164
  356. package/validation/tests/jobProperties/targetSmall.json +0 -152
  357. package/validation/tests/jobProperties/targetTiny.json +0 -142
  358. package/validation/tests/targets/focOp/bad.html +0 -25
  359. package/validation/tests/targets/linkTitle/index.html +0 -24
  360. package/validation/tests/targets/opFoc/bad.html +0 -26
  361. package/validation/tests/targets/opFoc/good.html +0 -23
@@ -1,5 +1,7 @@
1
+ "use strict";
1
2
  /*
2
3
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
4
+ © 2026 Jeff Witt.
3
5
  © 2025–2026 Jonathan Robert Pool.
4
6
 
5
7
  Licensed under the MIT License. See LICENSE file at the project root or
@@ -7,356 +9,353 @@
7
9
 
8
10
  SPDX-License-Identifier: MIT
9
11
  */
10
-
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.reporter = void 0;
14
+ const xPath_1 = require("../procs/xPath");
11
15
  /*
12
16
  buttonMenu
13
17
  This test reports nonstandard navigation among menu items of button-controlled menus. Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#menu. The trialKeys argument is an array of strings, each of which may be 'Home', 'End', '+', or '-'. The '+' string represents the ArrowDown or ArrowRight key, and the '-' string represents the ArrowUp or ArrowLeft key, depending on the orientation of the current menu. When the trialKeys argument is missing or is an empty array, 12 keys are selected at random.
18
+ Compiled to buttonMenu.js by tsc (issue #73); edit this file, not the emitted one.
14
19
  */
15
-
16
- // IMPORTS
17
-
18
- const {getXPathCatalogIndex} = require('../procs/xPath');
19
-
20
20
  // ########## FUNCTIONS
21
-
22
21
  // Returns data about the element referenced by a locator.
23
- const getLocatorData = async loc => {
24
- const locCount = await loc.count();
25
- // If the locator identifies exactly 1 element:
26
- if (locCount === 1) {
27
- // Get the facts obtainable from the browser.
28
- const data = await loc.evaluate(element => {
29
- // Tag name.
30
- const tagName = element.tagName;
31
- // ID.
32
- const id = element.id || '';
33
- // Texts.
34
- const {textContent} = element;
35
- const alts = Array.from(element.querySelectorAll('img[alt]:not([alt=""])'));
36
- const altTexts = alts.map(alt => alt.getAttribute('alt'));
37
- const altsText = altTexts.join(' ');
38
- const ariaLabelText = element.ariaLabel || '';
39
- const refLabelID = element.getAttribute('aria-labelledby');
40
- const refLabel = refLabelID ? document.getElementById(refLabelID) : '';
41
- const refLabelText = refLabel ? refLabel.textContent : '';
42
- let labelsText = '';
43
- if (tagName === 'INPUT') {
44
- const labels = element.labels || [];
45
- const labelTexts = [];
46
- labels.forEach(label => {
47
- labelTexts.push(label.textContent);
48
- });
49
- labelsText = labelTexts.join(' ');
50
- }
51
- let text = [textContent, altsText, ariaLabelText, refLabelText, labelsText]
52
- .join(' ')
53
- .replace(/\s+/g, ' ')
54
- .trim();
55
- if (! text) {
56
- text = element.outerHTML.replace(/\s+/g, ' ').trim();
57
- }
58
- if (/^<[^<>]+>$/.test(text)) {
59
- text = element.parentElement.outerHTML.replace(/\s+/g, ' ').trim();
60
- }
61
- // Location.
62
- let location = {
63
- doc: 'dom',
64
- type: 'box',
65
- spec: {}
66
- };
67
- if (id) {
68
- location.type = 'selector';
69
- location.spec = `#${id}`;
70
- }
71
- // Return the data.
72
- return {
73
- tagName,
74
- id,
75
- location,
76
- excerpt: text
77
- };
78
- });
79
- // If an ID-based selector could not be defined:
80
- if (data.location.type === 'box') {
81
- // Define a bounding-box-based location.
82
- const rawSpec = await loc.boundingBox();
83
- // If there is a bounding box (i.e. the element is visible):
84
- if (rawSpec) {
85
- // Populate the location.
86
- Object.keys(rawSpec).forEach(specName => {
87
- data.location.spec[specName] = Math.round(rawSpec[specName]);
22
+ const getLocatorData = async (loc) => {
23
+ const locCount = await loc.count();
24
+ // If the locator identifies exactly 1 element:
25
+ if (locCount === 1) {
26
+ // Get the facts obtainable from the browser.
27
+ const data = await loc.evaluate((element) => {
28
+ // Tag name.
29
+ const tagName = element.tagName;
30
+ // ID.
31
+ const id = element.id || '';
32
+ // Texts.
33
+ const { textContent } = element;
34
+ const alts = Array.from(element.querySelectorAll('img[alt]:not([alt=""])'));
35
+ const altTexts = alts.map(alt => alt.getAttribute('alt'));
36
+ const altsText = altTexts.join(' ');
37
+ const ariaLabelText = element.ariaLabel || '';
38
+ const refLabelID = element.getAttribute('aria-labelledby');
39
+ const refLabel = refLabelID ? document.getElementById(refLabelID) : '';
40
+ const refLabelText = refLabel ? refLabel.textContent : '';
41
+ let labelsText = '';
42
+ if (tagName === 'INPUT') {
43
+ const labels = element.labels || [];
44
+ const labelTexts = [];
45
+ labels.forEach(label => {
46
+ labelTexts.push(label.textContent);
47
+ });
48
+ labelsText = labelTexts.join(' ');
49
+ }
50
+ let text = [textContent, altsText, ariaLabelText, refLabelText, labelsText]
51
+ .join(' ')
52
+ .replace(/\s+/g, ' ')
53
+ .trim();
54
+ if (!text) {
55
+ text = element.outerHTML.replace(/\s+/g, ' ').trim();
56
+ }
57
+ if (/^<[^<>]+>$/.test(text)) {
58
+ text = element.parentElement.outerHTML.replace(/\s+/g, ' ').trim();
59
+ }
60
+ // Location.
61
+ let location = {
62
+ doc: 'dom',
63
+ type: 'box',
64
+ spec: {}
65
+ };
66
+ if (id) {
67
+ location.type = 'selector';
68
+ location.spec = `#${id}`;
69
+ }
70
+ // Return the data.
71
+ return {
72
+ tagName,
73
+ id,
74
+ location,
75
+ excerpt: text
76
+ };
88
77
  });
89
- }
90
- // Otherwise, i.e. if there is no bounding box:
91
- else {
92
- // Empty the location.
93
- data.location.doc = '';
94
- data.location.type = '';
95
- data.location.spec = '';
96
- }
78
+ // If an ID-based selector could not be defined:
79
+ if (data.location.type === 'box') {
80
+ // Define a bounding-box-based location.
81
+ const rawSpec = await loc.boundingBox();
82
+ // If there is a bounding box (i.e. the element is visible):
83
+ if (rawSpec) {
84
+ // Populate the location.
85
+ Object.keys(rawSpec).forEach(specName => {
86
+ data.location.spec[specName] = Math.round(rawSpec[specName]);
87
+ });
88
+ }
89
+ // Otherwise, i.e. if there is no bounding box:
90
+ else {
91
+ // Empty the location.
92
+ data.location.doc = '';
93
+ data.location.type = '';
94
+ data.location.spec = '';
95
+ }
96
+ }
97
+ // If the text is long:
98
+ if (data.excerpt.length > 400) {
99
+ // Truncate its middle.
100
+ data.excerpt = `${data.excerpt.slice(0, 200)} … ${data.excerpt.slice(-200)}`;
101
+ }
102
+ // Return the data.
103
+ return data;
97
104
  }
98
- // If the text is long:
99
- if (data.excerpt.length > 400) {
100
- // Truncate its middle.
101
- data.excerpt = `${data.excerpt.slice(0, 200)} … ${data.excerpt.slice(-200)}`;
105
+ // Otherwise, i.e. if it does not identify exactly 1 element:
106
+ else {
107
+ // Report this.
108
+ console.log(`ERROR: Locator count to get data from is ${locCount} instead of 1`);
109
+ return null;
102
110
  }
103
- // Return the data.
104
- return data;
105
- }
106
- // Otherwise, i.e. if it does not identify exactly 1 element:
107
- else {
108
- // Report this.
109
- console.log(`ERROR: Locator count to get data from is ${locCount} instead of 1`);
110
- return null;
111
- }
112
111
  };
113
112
  // Returns an adjacent index, with wrapping.
114
113
  const getAdjacentIndexWithWrap = (groupSize, startIndex, increment) => {
115
- let newIndex = startIndex + increment;
116
- if (newIndex < 0) {
117
- newIndex = groupSize - 1;
118
- }
119
- else if (newIndex > groupSize - 1) {
120
- newIndex = 0;
121
- }
122
- return newIndex;
114
+ let newIndex = startIndex + increment;
115
+ if (newIndex < 0) {
116
+ newIndex = groupSize - 1;
117
+ }
118
+ else if (newIndex > groupSize - 1) {
119
+ newIndex = 0;
120
+ }
121
+ return newIndex;
123
122
  };
124
123
  // Returns whether a key created the expected effective focus.
125
124
  const focusSuccess = async (miLocsDir, priorIndex, key, isPseudo) => {
126
- // Initialize the result.
127
- const result = {
128
- isOK: false,
129
- newFocIndex: null
130
- };
131
- // For each of the menu items directly descending from the menu:
132
- for (const miLocDir of miLocsDir) {
133
- // Get whether it is effectively focused.
134
- const hasFocus = await miLocDir.evaluate((el, isPseudo) => {
135
- if (isPseudo) {
136
- const effectiveID = document.activeElement.getAttribute('aria-activeDescendant');
137
- if (effectiveID) {
138
- const effectiveEl = document.getElementById(effectiveID);
139
- return effectiveEl === el;
140
- }
141
- else {
142
- return false;
143
- }
144
- }
145
- else {
146
- return document.activeElement === el;
147
- }
148
- }, isPseudo);
149
- // If it is:
150
- if (hasFocus) {
151
- // Get whether the effective focus is as expected and update the result.
152
- const miFocIndex = miLocsDir.indexOf(miLocDir);
153
- const miCount = miLocsDir.length;
154
- if (key === 'Home' && miFocIndex === 0) {
155
- result.isOK = true;
156
- result.newFocIndex = 0;
157
- }
158
- else if (key === 'End' && miFocIndex === miCount - 1) {
159
- result.isOK = true;
160
- result.newFocIndex = miCount - 1;
161
- }
162
- else if (['ArrowLeft', 'ArrowUp'].includes(key)) {
163
- const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, -1);
164
- if (miFocIndex === expectedIndex) {
165
- result.isOK = true;
166
- result.newFocIndex = expectedIndex;
167
- }
168
- }
169
- else if (['ArrowRight', 'ArrowDown'].includes(key)) {
170
- const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, 1);
171
- if (miFocIndex === expectedIndex) {
172
- result.isOK = true;
173
- result.newFocIndex = expectedIndex;
174
- }
175
- }
176
- break;
177
- }
178
- }
179
- // Return the result.
180
- return result;
181
- };
182
- // Performs the test and reports the result.
183
- exports.reporter = async (page, report, _0, withItems, trialKeySpecs = []) => {
184
- // Initialize the result.
185
- const data = {};
186
- const totals = [0, 0, 0, 0];
187
- const standardInstances = [];
188
- // Get locators for all menu buttons.
189
- const mbLocAll = page.locator(
190
- 'button[aria-controls][aria-expanded][aria-haspopup=true], button[aria-controls][aria-expanded][aria-haspopup=menu]'
191
- );
192
- const mbLocsAll = await mbLocAll.all();
193
- // For each menu button:
194
- for (const mbLoc of mbLocsAll) {
195
- // Get a locator for its menu.
196
- const menuID = await mbLoc.getAttribute('aria-controls');
197
- const menuLoc = page.locator(`[id=${menuID}][role=menu], [id=${menuID}][role=menubar]`);
198
- // If the button controls exactly 1 menu:
199
- if (menuLoc && await menuLoc.count() === 1) {
200
- // Get data on the menu.
201
- const elData = await getLocatorData(menuLoc);
202
- // If data were obtained:
203
- if (elData) {
204
- // Get the orientation and focus-management type of the menu.
205
- const extraData = await menuLoc.evaluate(element => {
206
- const extraData = {};
207
- let orientation = element.getAttribute('aria-orientation');
208
- if (! orientation) {
209
- const role = element.getAttribute('role');
210
- if (role === 'menubar') {
211
- orientation = 'horizontal';
212
- }
213
- else if (role === 'menu') {
214
- orientation = 'vertical';
125
+ // Initialize the result.
126
+ const result = {
127
+ isOK: false,
128
+ newFocIndex: null
129
+ };
130
+ // For each of the menu items directly descending from the menu:
131
+ for (const miLocDir of miLocsDir) {
132
+ // Get whether it is effectively focused.
133
+ const hasFocus = await miLocDir.evaluate((el, isPseudo) => {
134
+ if (isPseudo) {
135
+ // The page has an active element whenever this runs; verbatim from the original.
136
+ const effectiveID = document.activeElement.getAttribute('aria-activeDescendant');
137
+ if (effectiveID) {
138
+ const effectiveEl = document.getElementById(effectiveID);
139
+ return effectiveEl === el;
140
+ }
141
+ else {
142
+ return false;
143
+ }
215
144
  }
216
145
  else {
217
- orientation = null;
146
+ return document.activeElement === el;
218
147
  }
219
- }
220
- extraData.orientation = orientation;
221
- const isPseudo = !! element.getAttribute('aria-activedescendant');
222
- extraData.isPseudo = isPseudo;
223
- return extraData;
224
- });
225
- // If they were obtained:
226
- if (extraData) {
227
- // Get locators for its descendant non-menu menu items.
228
- const miLocAll = menuLoc.locator('[role=menuitem]:not([role=menu], [role=menubar])');
229
- // Get which of them are direct descendants.
230
- const areDirect = await miLocAll.evaluateAll((els, menuID) => {
231
- return els.map(el => {
232
- const itsMenu = el.closest('[role=menu], [role=menubar]');
233
- return itsMenu.id && itsMenu.id === menuID;
234
- });
235
- }, menuID);
236
- const miLocsAll = await miLocAll.all();
237
- const miLocsDir = miLocsAll.filter((loc, index) => areDirect[index]);
238
- // If there are at least 2 of them:
239
- if (miLocsDir.length > 1) {
240
- // Ensure that the menu is collapsed.
241
- await menuLoc.evaluate(element => {
242
- element.setAttribute('aria-expanded', false);
243
- });
244
- // Focus the menu button and press the key that opens its menu from the top.
245
- await mbLoc.press(extraData.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown');
246
- // If trial keys have been specified:
247
- const trialKeys = [];
248
- if (trialKeySpecs.length) {
249
- // Implement them.
250
- trialKeySpecs.forEach(spec => {
251
- if (['Home', 'End'].includes(spec)) {
252
- trialKeys.push(spec);
148
+ }, isPseudo);
149
+ // If it is:
150
+ if (hasFocus) {
151
+ // Get whether the effective focus is as expected and update the result.
152
+ const miFocIndex = miLocsDir.indexOf(miLocDir);
153
+ const miCount = miLocsDir.length;
154
+ if (key === 'Home' && miFocIndex === 0) {
155
+ result.isOK = true;
156
+ result.newFocIndex = 0;
157
+ }
158
+ else if (key === 'End' && miFocIndex === miCount - 1) {
159
+ result.isOK = true;
160
+ result.newFocIndex = miCount - 1;
161
+ }
162
+ else if (['ArrowLeft', 'ArrowUp'].includes(key)) {
163
+ const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, -1);
164
+ if (miFocIndex === expectedIndex) {
165
+ result.isOK = true;
166
+ result.newFocIndex = expectedIndex;
253
167
  }
254
- else if (spec === '-') {
255
- trialKeys.push(extraData.orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp');
168
+ }
169
+ else if (['ArrowRight', 'ArrowDown'].includes(key)) {
170
+ const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, 1);
171
+ if (miFocIndex === expectedIndex) {
172
+ result.isOK = true;
173
+ result.newFocIndex = expectedIndex;
256
174
  }
257
- else if (spec === '+') {
258
- trialKeys.push(
259
- extraData.orientation === 'horizontal' ? 'ArrowtRight' : 'ArrowDown'
260
- );
175
+ }
176
+ break;
177
+ }
178
+ }
179
+ // Return the result.
180
+ return result;
181
+ };
182
+ // Performs the test and reports the result.
183
+ const reporter = async (page, report, _0, withItems, trialKeySpecs = []) => {
184
+ // Initialize the result.
185
+ const data = {};
186
+ const totals = [0, 0, 0, 0];
187
+ const standardInstances = [];
188
+ // Get locators for all menu buttons.
189
+ const mbLocAll = page.locator('button[aria-controls][aria-expanded][aria-haspopup=true], button[aria-controls][aria-expanded][aria-haspopup=menu]');
190
+ const mbLocsAll = await mbLocAll.all();
191
+ // For each menu button:
192
+ for (const mbLoc of mbLocsAll) {
193
+ // Get a locator for its menu.
194
+ const menuID = await mbLoc.getAttribute('aria-controls');
195
+ const menuLoc = page.locator(`[id=${menuID}][role=menu], [id=${menuID}][role=menubar]`);
196
+ // If the button controls exactly 1 menu:
197
+ if (menuLoc && await menuLoc.count() === 1) {
198
+ // Get data on the menu.
199
+ const elData = await getLocatorData(menuLoc);
200
+ // If data were obtained:
201
+ if (elData) {
202
+ // Get the orientation and focus-management type of the menu.
203
+ const extraData = await menuLoc.evaluate(element => {
204
+ const extraData = {};
205
+ let orientation = element.getAttribute('aria-orientation');
206
+ if (!orientation) {
207
+ const role = element.getAttribute('role');
208
+ if (role === 'menubar') {
209
+ orientation = 'horizontal';
210
+ }
211
+ else if (role === 'menu') {
212
+ orientation = 'vertical';
213
+ }
214
+ else {
215
+ orientation = null;
216
+ }
217
+ }
218
+ extraData.orientation = orientation;
219
+ const isPseudo = !!element.getAttribute('aria-activedescendant');
220
+ extraData.isPseudo = isPseudo;
221
+ return extraData;
222
+ });
223
+ // If they were obtained:
224
+ if (extraData) {
225
+ // Get locators for its descendant non-menu menu items.
226
+ const miLocAll = menuLoc.locator('[role=menuitem]:not([role=menu], [role=menubar])');
227
+ // Get which of them are direct descendants.
228
+ const areDirect = await miLocAll.evaluateAll((els, menuID) => {
229
+ return els.map(el => {
230
+ // Menu items are inside a menu by definition; verbatim from the original.
231
+ const itsMenu = el.closest('[role=menu], [role=menubar]');
232
+ return itsMenu.id && itsMenu.id === menuID;
233
+ });
234
+ }, menuID);
235
+ const miLocsAll = await miLocAll.all();
236
+ const miLocsDir = miLocsAll.filter((loc, index) => areDirect[index]);
237
+ // If there are at least 2 of them:
238
+ if (miLocsDir.length > 1) {
239
+ // Ensure that the menu is collapsed.
240
+ await menuLoc.evaluate(element => {
241
+ // The boolean is coerced to 'false' at runtime; verbatim from the original.
242
+ element.setAttribute('aria-expanded', false);
243
+ });
244
+ // Focus the menu button and press the key that opens its menu from the top.
245
+ await mbLoc.press(extraData.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown');
246
+ // If trial keys have been specified:
247
+ const trialKeys = [];
248
+ if (trialKeySpecs.length) {
249
+ // Implement them.
250
+ trialKeySpecs.forEach(spec => {
251
+ if (['Home', 'End'].includes(spec)) {
252
+ trialKeys.push(spec);
253
+ }
254
+ else if (spec === '-') {
255
+ trialKeys.push(extraData.orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp');
256
+ }
257
+ else if (spec === '+') {
258
+ trialKeys.push(extraData.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown');
259
+ }
260
+ });
261
+ }
262
+ // Otherwise, i.e. if trial keys have not been specified:
263
+ else {
264
+ // Randomly draw 12 keys from Home, End, and the applicable arrow keys.
265
+ const keys = ['Home', 'End'];
266
+ if (extraData.orientation === 'horizontal') {
267
+ keys.push('ArrowLeft', 'ArrowRight');
268
+ }
269
+ else {
270
+ keys.push('ArrowUp', 'ArrowDown');
271
+ }
272
+ let trialKeyCount = 0;
273
+ while (trialKeyCount++ < 12) {
274
+ trialKeys.push(keys[Math.floor(4 * Math.random())]);
275
+ }
276
+ }
277
+ // Add the list of trial keys to the result.
278
+ data.trialKeys = trialKeys;
279
+ let focIndex = 0;
280
+ // For each key in the trial:
281
+ for (const key of trialKeys) {
282
+ // Press it.
283
+ await page.keyboard.press(key);
284
+ // Get whether the expected focus occurred.
285
+ const focData = await focusSuccess(miLocsDir, focIndex, key, extraData.isPseudo);
286
+ // If so:
287
+ if (focData.isOK) {
288
+ // Update the index of the effective focus.
289
+ focIndex = focData.newFocIndex;
290
+ }
291
+ // Otherwise, i.e. if the expected focus did not occur:
292
+ else {
293
+ // Add to the totals.
294
+ totals[2]++;
295
+ // If itemization is required:
296
+ if (withItems) {
297
+ // Get the XPath of the menu button.
298
+ const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
299
+ // Add an instance to the standard instances.
300
+ standardInstances.push({
301
+ ruleID: 'buttonMenu',
302
+ what: `Menu responds nonstandardly to the ${key} key`,
303
+ ordinalSeverity: 2,
304
+ count: 1,
305
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, mbXPath)
306
+ });
307
+ }
308
+ // Stop testing the menu button.
309
+ break;
310
+ }
311
+ }
312
+ }
313
+ }
314
+ // Otherwise, i.e. if the orientation and focus-management type were not obtained:
315
+ else {
316
+ // Report this.
317
+ console.log('ERROR: Menu orientation and focus-management type not obtained');
261
318
  }
262
- });
263
319
  }
264
- // Otherwise, i.e. if trial keys have not been specified:
320
+ // Otherwise, i.e. if the data were not obtained:
265
321
  else {
266
- // Randomly draw 12 keys from Home, End, and the applicable arrow keys.
267
- const keys = ['Home', 'End'];
268
- if (extraData.orientation === 'horizontal') {
269
- keys.push('ArrowLeft', 'ArrowRight');
270
- }
271
- else {
272
- keys.push('ArrowUp', 'ArrowDown');
273
- }
274
- let trialKeyCount = 0;
275
- while (trialKeyCount++ < 12) {
276
- trialKeys.push(keys[Math.floor(4 * Math.random())]);
277
- }
322
+ // Report this.
323
+ console.log('ERROR: Menu data not obtained');
278
324
  }
279
- // Add the list of trial keys to the result.
280
- data.trialKeys = trialKeys;
281
- let focIndex = 0;
282
- // For each key in the trial:
283
- for (const key of trialKeys) {
284
- // Press it.
285
- await page.keyboard.press(key);
286
- // Get whether the expected focus occurred.
287
- const focData = await focusSuccess(miLocsDir, focIndex, key, extraData.isPseudo);
288
- // If so:
289
- if (focData.isOK) {
290
- // Update the index of the effective focus.
291
- focIndex = focData.newFocIndex;
292
- }
293
- // Otherwise, i.e. if the expected focus did not occur:
294
- else {
295
- // Add to the totals.
296
- totals[2]++;
297
- // If itemization is required:
298
- if (withItems) {
299
- // Get the XPath of the menu button.
300
- const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
301
- // Add an instance to the standard instances.
302
- standardInstances.push({
325
+ }
326
+ // Otherwise, i.e. if it does not control exactly 1 menu:
327
+ else {
328
+ // Add to the totals.
329
+ totals[2]++;
330
+ // If itemization is required:
331
+ if (withItems) {
332
+ // Get the XPath of the menu button.
333
+ const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
334
+ // Add an instance to the standard instances.
335
+ standardInstances.push({
303
336
  ruleID: 'buttonMenu',
304
- what: `Menu responds nonstandardly to the ${key} key`,
337
+ what: 'Menu button does not control exactly 1 menu',
305
338
  ordinalSeverity: 2,
306
339
  count: 1,
307
- catalogIndex: getXPathCatalogIndex(report, mbXPath)
308
- });
309
- }
310
- // Stop testing the menu button.
311
- break;
312
- }
340
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, mbXPath)
341
+ });
313
342
  }
314
- }
315
- }
316
- // Otherwise, i.e. if the orientation and focus-management type were not obtained:
317
- else {
318
- // Report this.
319
- console.log('ERROR: Menu orientation and focus-management type not obtained');
320
343
  }
321
- }
322
- // Otherwise, i.e. if the data were not obtained:
323
- else {
324
- // Report this.
325
- console.log('ERROR: Menu data not obtained');
326
- }
327
344
  }
328
- // Otherwise, i.e. if it does not control exactly 1 menu:
329
- else {
330
- // Add to the totals.
331
- totals[2]++;
332
- // If itemization is required:
333
- if (withItems) {
334
- // Get the XPath of the menu button.
335
- const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
336
- // Add an instance to the standard instances.
345
+ // If itemization is not required and there are any instances:
346
+ if (!withItems && totals[2]) {
347
+ // Add a summary instance to the result.
337
348
  standardInstances.push({
338
- ruleID: 'buttonMenu',
339
- what: 'Menu button does not control exactly 1 menu',
340
- ordinalSeverity: 2,
341
- count: 1,
342
- catalogIndex: getXPathCatalogIndex(report, mbXPath)
349
+ ruleID: 'buttonMenu',
350
+ what: 'Menu buttons and menus behave nonstandardly',
351
+ count: totals[2],
352
+ ordinalSeverity: 2
343
353
  });
344
- }
345
354
  }
346
- }
347
- // If itemization is not required and there are any instances:
348
- if (! withItems && totals[2]) {
349
- // Add a summary instance to the result.
350
- standardInstances.push({
351
- ruleID: 'buttonMenu',
352
- what: 'Menu buttons and menus behave nonstandardly',
353
- count: totals[2],
354
- ordinalSeverity: 2
355
- });
356
- }
357
- return {
358
- data,
359
- totals,
360
- standardInstances
361
- };
355
+ return {
356
+ data,
357
+ totals,
358
+ standardInstances
359
+ };
362
360
  };
361
+ exports.reporter = reporter;