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
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
4
  © 2026 Jeff Witt.
@@ -8,356 +9,353 @@
8
9
 
9
10
  SPDX-License-Identifier: MIT
10
11
  */
11
-
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.reporter = void 0;
14
+ const xPath_1 = require("../procs/xPath");
12
15
  /*
13
16
  buttonMenu
14
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.
15
19
  */
16
-
17
- // IMPORTS
18
-
19
- const {getXPathCatalogIndex} = require('../procs/xPath');
20
-
21
20
  // ########## FUNCTIONS
22
-
23
21
  // Returns data about the element referenced by a locator.
24
- const getLocatorData = async loc => {
25
- const locCount = await loc.count();
26
- // If the locator identifies exactly 1 element:
27
- if (locCount === 1) {
28
- // Get the facts obtainable from the browser.
29
- const data = await loc.evaluate(element => {
30
- // Tag name.
31
- const tagName = element.tagName;
32
- // ID.
33
- const id = element.id || '';
34
- // Texts.
35
- const {textContent} = element;
36
- const alts = Array.from(element.querySelectorAll('img[alt]:not([alt=""])'));
37
- const altTexts = alts.map(alt => alt.getAttribute('alt'));
38
- const altsText = altTexts.join(' ');
39
- const ariaLabelText = element.ariaLabel || '';
40
- const refLabelID = element.getAttribute('aria-labelledby');
41
- const refLabel = refLabelID ? document.getElementById(refLabelID) : '';
42
- const refLabelText = refLabel ? refLabel.textContent : '';
43
- let labelsText = '';
44
- if (tagName === 'INPUT') {
45
- const labels = element.labels || [];
46
- const labelTexts = [];
47
- labels.forEach(label => {
48
- labelTexts.push(label.textContent);
49
- });
50
- labelsText = labelTexts.join(' ');
51
- }
52
- let text = [textContent, altsText, ariaLabelText, refLabelText, labelsText]
53
- .join(' ')
54
- .replace(/\s+/g, ' ')
55
- .trim();
56
- if (! text) {
57
- text = element.outerHTML.replace(/\s+/g, ' ').trim();
58
- }
59
- if (/^<[^<>]+>$/.test(text)) {
60
- text = element.parentElement.outerHTML.replace(/\s+/g, ' ').trim();
61
- }
62
- // Location.
63
- let location = {
64
- doc: 'dom',
65
- type: 'box',
66
- spec: {}
67
- };
68
- if (id) {
69
- location.type = 'selector';
70
- location.spec = `#${id}`;
71
- }
72
- // Return the data.
73
- return {
74
- tagName,
75
- id,
76
- location,
77
- excerpt: text
78
- };
79
- });
80
- // If an ID-based selector could not be defined:
81
- if (data.location.type === 'box') {
82
- // Define a bounding-box-based location.
83
- const rawSpec = await loc.boundingBox();
84
- // If there is a bounding box (i.e. the element is visible):
85
- if (rawSpec) {
86
- // Populate the location.
87
- Object.keys(rawSpec).forEach(specName => {
88
- 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
+ };
89
77
  });
90
- }
91
- // Otherwise, i.e. if there is no bounding box:
92
- else {
93
- // Empty the location.
94
- data.location.doc = '';
95
- data.location.type = '';
96
- data.location.spec = '';
97
- }
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;
98
104
  }
99
- // If the text is long:
100
- if (data.excerpt.length > 400) {
101
- // Truncate its middle.
102
- 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;
103
110
  }
104
- // Return the data.
105
- return data;
106
- }
107
- // Otherwise, i.e. if it does not identify exactly 1 element:
108
- else {
109
- // Report this.
110
- console.log(`ERROR: Locator count to get data from is ${locCount} instead of 1`);
111
- return null;
112
- }
113
111
  };
114
112
  // Returns an adjacent index, with wrapping.
115
113
  const getAdjacentIndexWithWrap = (groupSize, startIndex, increment) => {
116
- let newIndex = startIndex + increment;
117
- if (newIndex < 0) {
118
- newIndex = groupSize - 1;
119
- }
120
- else if (newIndex > groupSize - 1) {
121
- newIndex = 0;
122
- }
123
- 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;
124
122
  };
125
123
  // Returns whether a key created the expected effective focus.
126
124
  const focusSuccess = async (miLocsDir, priorIndex, key, isPseudo) => {
127
- // Initialize the result.
128
- const result = {
129
- isOK: false,
130
- newFocIndex: null
131
- };
132
- // For each of the menu items directly descending from the menu:
133
- for (const miLocDir of miLocsDir) {
134
- // Get whether it is effectively focused.
135
- const hasFocus = await miLocDir.evaluate((el, isPseudo) => {
136
- if (isPseudo) {
137
- const effectiveID = document.activeElement.getAttribute('aria-activeDescendant');
138
- if (effectiveID) {
139
- const effectiveEl = document.getElementById(effectiveID);
140
- return effectiveEl === el;
141
- }
142
- else {
143
- return false;
144
- }
145
- }
146
- else {
147
- return document.activeElement === el;
148
- }
149
- }, isPseudo);
150
- // If it is:
151
- if (hasFocus) {
152
- // Get whether the effective focus is as expected and update the result.
153
- const miFocIndex = miLocsDir.indexOf(miLocDir);
154
- const miCount = miLocsDir.length;
155
- if (key === 'Home' && miFocIndex === 0) {
156
- result.isOK = true;
157
- result.newFocIndex = 0;
158
- }
159
- else if (key === 'End' && miFocIndex === miCount - 1) {
160
- result.isOK = true;
161
- result.newFocIndex = miCount - 1;
162
- }
163
- else if (['ArrowLeft', 'ArrowUp'].includes(key)) {
164
- const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, -1);
165
- if (miFocIndex === expectedIndex) {
166
- result.isOK = true;
167
- result.newFocIndex = expectedIndex;
168
- }
169
- }
170
- else if (['ArrowRight', 'ArrowDown'].includes(key)) {
171
- const expectedIndex = getAdjacentIndexWithWrap(miCount, priorIndex, 1);
172
- if (miFocIndex === expectedIndex) {
173
- result.isOK = true;
174
- result.newFocIndex = expectedIndex;
175
- }
176
- }
177
- break;
178
- }
179
- }
180
- // Return the result.
181
- return result;
182
- };
183
- // Performs the test and reports the result.
184
- exports.reporter = async (page, report, _0, withItems, trialKeySpecs = []) => {
185
- // Initialize the result.
186
- const data = {};
187
- const totals = [0, 0, 0, 0];
188
- const standardInstances = [];
189
- // Get locators for all menu buttons.
190
- const mbLocAll = page.locator(
191
- 'button[aria-controls][aria-expanded][aria-haspopup=true], button[aria-controls][aria-expanded][aria-haspopup=menu]'
192
- );
193
- const mbLocsAll = await mbLocAll.all();
194
- // For each menu button:
195
- for (const mbLoc of mbLocsAll) {
196
- // Get a locator for its menu.
197
- const menuID = await mbLoc.getAttribute('aria-controls');
198
- const menuLoc = page.locator(`[id=${menuID}][role=menu], [id=${menuID}][role=menubar]`);
199
- // If the button controls exactly 1 menu:
200
- if (menuLoc && await menuLoc.count() === 1) {
201
- // Get data on the menu.
202
- const elData = await getLocatorData(menuLoc);
203
- // If data were obtained:
204
- if (elData) {
205
- // Get the orientation and focus-management type of the menu.
206
- const extraData = await menuLoc.evaluate(element => {
207
- const extraData = {};
208
- let orientation = element.getAttribute('aria-orientation');
209
- if (! orientation) {
210
- const role = element.getAttribute('role');
211
- if (role === 'menubar') {
212
- orientation = 'horizontal';
213
- }
214
- else if (role === 'menu') {
215
- 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
+ }
216
144
  }
217
145
  else {
218
- orientation = null;
146
+ return document.activeElement === el;
219
147
  }
220
- }
221
- extraData.orientation = orientation;
222
- const isPseudo = !! element.getAttribute('aria-activedescendant');
223
- extraData.isPseudo = isPseudo;
224
- return extraData;
225
- });
226
- // If they were obtained:
227
- if (extraData) {
228
- // Get locators for its descendant non-menu menu items.
229
- const miLocAll = menuLoc.locator('[role=menuitem]:not([role=menu], [role=menubar])');
230
- // Get which of them are direct descendants.
231
- const areDirect = await miLocAll.evaluateAll((els, menuID) => {
232
- return els.map(el => {
233
- const itsMenu = el.closest('[role=menu], [role=menubar]');
234
- return itsMenu.id && itsMenu.id === menuID;
235
- });
236
- }, menuID);
237
- const miLocsAll = await miLocAll.all();
238
- const miLocsDir = miLocsAll.filter((loc, index) => areDirect[index]);
239
- // If there are at least 2 of them:
240
- if (miLocsDir.length > 1) {
241
- // Ensure that the menu is collapsed.
242
- await menuLoc.evaluate(element => {
243
- element.setAttribute('aria-expanded', false);
244
- });
245
- // Focus the menu button and press the key that opens its menu from the top.
246
- await mbLoc.press(extraData.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown');
247
- // If trial keys have been specified:
248
- const trialKeys = [];
249
- if (trialKeySpecs.length) {
250
- // Implement them.
251
- trialKeySpecs.forEach(spec => {
252
- if (['Home', 'End'].includes(spec)) {
253
- 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;
254
167
  }
255
- else if (spec === '-') {
256
- 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;
257
174
  }
258
- else if (spec === '+') {
259
- trialKeys.push(
260
- extraData.orientation === 'horizontal' ? 'ArrowtRight' : 'ArrowDown'
261
- );
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');
262
318
  }
263
- });
264
319
  }
265
- // Otherwise, i.e. if trial keys have not been specified:
320
+ // Otherwise, i.e. if the data were not obtained:
266
321
  else {
267
- // Randomly draw 12 keys from Home, End, and the applicable arrow keys.
268
- const keys = ['Home', 'End'];
269
- if (extraData.orientation === 'horizontal') {
270
- keys.push('ArrowLeft', 'ArrowRight');
271
- }
272
- else {
273
- keys.push('ArrowUp', 'ArrowDown');
274
- }
275
- let trialKeyCount = 0;
276
- while (trialKeyCount++ < 12) {
277
- trialKeys.push(keys[Math.floor(4 * Math.random())]);
278
- }
322
+ // Report this.
323
+ console.log('ERROR: Menu data not obtained');
279
324
  }
280
- // Add the list of trial keys to the result.
281
- data.trialKeys = trialKeys;
282
- let focIndex = 0;
283
- // For each key in the trial:
284
- for (const key of trialKeys) {
285
- // Press it.
286
- await page.keyboard.press(key);
287
- // Get whether the expected focus occurred.
288
- const focData = await focusSuccess(miLocsDir, focIndex, key, extraData.isPseudo);
289
- // If so:
290
- if (focData.isOK) {
291
- // Update the index of the effective focus.
292
- focIndex = focData.newFocIndex;
293
- }
294
- // Otherwise, i.e. if the expected focus did not occur:
295
- else {
296
- // Add to the totals.
297
- totals[2]++;
298
- // If itemization is required:
299
- if (withItems) {
300
- // Get the XPath of the menu button.
301
- const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
302
- // Add an instance to the standard instances.
303
- 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({
304
336
  ruleID: 'buttonMenu',
305
- what: `Menu responds nonstandardly to the ${key} key`,
337
+ what: 'Menu button does not control exactly 1 menu',
306
338
  ordinalSeverity: 2,
307
339
  count: 1,
308
- catalogIndex: getXPathCatalogIndex(report, mbXPath)
309
- });
310
- }
311
- // Stop testing the menu button.
312
- break;
313
- }
340
+ catalogIndex: (0, xPath_1.getXPathCatalogIndex)(report, mbXPath)
341
+ });
314
342
  }
315
- }
316
- }
317
- // Otherwise, i.e. if the orientation and focus-management type were not obtained:
318
- else {
319
- // Report this.
320
- console.log('ERROR: Menu orientation and focus-management type not obtained');
321
343
  }
322
- }
323
- // Otherwise, i.e. if the data were not obtained:
324
- else {
325
- // Report this.
326
- console.log('ERROR: Menu data not obtained');
327
- }
328
344
  }
329
- // Otherwise, i.e. if it does not control exactly 1 menu:
330
- else {
331
- // Add to the totals.
332
- totals[2]++;
333
- // If itemization is required:
334
- if (withItems) {
335
- // Get the XPath of the menu button.
336
- const mbXPath = await mbLoc.evaluate(element => window.getXPath(element));
337
- // 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.
338
348
  standardInstances.push({
339
- ruleID: 'buttonMenu',
340
- what: 'Menu button does not control exactly 1 menu',
341
- ordinalSeverity: 2,
342
- count: 1,
343
- catalogIndex: getXPathCatalogIndex(report, mbXPath)
349
+ ruleID: 'buttonMenu',
350
+ what: 'Menu buttons and menus behave nonstandardly',
351
+ count: totals[2],
352
+ ordinalSeverity: 2
344
353
  });
345
- }
346
354
  }
347
- }
348
- // If itemization is not required and there are any instances:
349
- if (! withItems && totals[2]) {
350
- // Add a summary instance to the result.
351
- standardInstances.push({
352
- ruleID: 'buttonMenu',
353
- what: 'Menu buttons and menus behave nonstandardly',
354
- count: totals[2],
355
- ordinalSeverity: 2
356
- });
357
- }
358
- return {
359
- data,
360
- totals,
361
- standardInstances
362
- };
355
+ return {
356
+ data,
357
+ totals,
358
+ standardInstances
359
+ };
363
360
  };
361
+ exports.reporter = reporter;