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
@@ -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;