testaro 39.0.3 → 40.0.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "39.0.3",
3
+ "version": "40.0.2",
4
4
  "description": "Run 1000 web accessibility tests from 10 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/testaro/role.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2021–2023 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
@@ -22,133 +22,18 @@
22
22
 
23
23
  /*
24
24
  role
25
- This test reports role assignment that violate either an applicable standard or an applicable
26
- recommendation from WAI-ARIA. Invalid roles include those that are abstract and thus prohibited
27
- from direct use, and those that are implicit in HTML elements and thus advised against. Roles
28
- that explicitly confirm implicit roles are deemed redundant and can be scored as less serious
29
- than roles that override implicit roles. The math role has been removed, because of poor
30
- adoption and exclusion from HTML5. The img role has accessibility uses, so is not classified
31
- as deprecated. See:
32
- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Role_Img
33
- https://www.w3.org/TR/html-aria/
34
- https://www.w3.org/TR/wai-aria/#roles_categorization
25
+ This test reports elements with native-replacing explicit role attributes.
35
26
  */
36
- exports.reporter = async page => await page.evaluate(() => {
37
27
 
38
- // CONSTANTS
28
+ // IMPORTS
29
+
30
+ // Module to perform common operations.
31
+ const {init, report} = require('../procs/testaro');
32
+
33
+ // CONSTANTS
39
34
 
40
- // All roles implicit in HTML elements.
41
- const badRoles = new Set([
42
- 'article',
43
- 'banner',
44
- 'button',
45
- 'cell',
46
- 'checkbox',
47
- 'columnheader',
48
- 'combobox',
49
- 'complementary',
50
- 'contentinfo',
51
- 'definition',
52
- 'figure',
53
- 'graphics-document',
54
- 'gridcell',
55
- 'group',
56
- 'heading',
57
- 'link',
58
- 'list',
59
- 'listbox',
60
- 'listitem',
61
- 'main',
62
- 'navigation',
63
- 'option',
64
- 'progressbar',
65
- 'radio',
66
- 'row',
67
- 'rowgroup',
68
- 'rowheader',
69
- 'searchbox',
70
- 'separator',
71
- 'slider',
72
- 'spinbutton',
73
- 'status',
74
- 'table',
75
- 'term',
76
- 'textbox'
77
- ]);
78
- // All non-abstract roles.
79
- const goodRoles = new Set([
80
- 'alert',
81
- 'alertdialog',
82
- 'application',
83
- 'article',
84
- 'banner',
85
- 'button',
86
- 'cell',
87
- 'checkbox',
88
- 'columnheader',
89
- 'combobox',
90
- 'complementary',
91
- 'contentinfo',
92
- 'definition',
93
- 'dialog',
94
- 'directory',
95
- 'document',
96
- 'feed',
97
- 'figure',
98
- 'form',
99
- 'grid',
100
- 'gridcell',
101
- 'group',
102
- 'heading',
103
- 'img',
104
- 'link',
105
- 'list',
106
- 'listbox',
107
- 'listitem',
108
- 'log',
109
- 'main',
110
- 'marquee',
111
- 'menu',
112
- 'menubar',
113
- 'menuitem',
114
- 'menuitemcheckbox',
115
- 'menuitemradio',
116
- 'navigation',
117
- 'none',
118
- 'note',
119
- 'option',
120
- 'presentation',
121
- 'progressbar',
122
- 'radio',
123
- 'radiogroup',
124
- 'region',
125
- 'row',
126
- 'rowgroup',
127
- 'rowheader',
128
- 'scrollbar',
129
- 'search',
130
- 'searchbox',
131
- 'separator',
132
- 'separator',
133
- 'slider',
134
- 'spinbutton',
135
- 'status',
136
- 'switch',
137
- 'tab',
138
- 'table',
139
- 'tablist',
140
- 'tabpanel',
141
- 'term',
142
- 'textbox',
143
- 'timer',
144
- 'toolbar',
145
- 'tooltip',
146
- 'tree',
147
- 'treegrid',
148
- 'treeitem',
149
- ]);
150
35
  // Implicit roles
151
- const implicitRoles = {
36
+ const roleImplications = {
152
37
  article: 'article',
153
38
  aside: 'complementary',
154
39
  button: 'button',
@@ -180,373 +65,28 @@ exports.reporter = async page => await page.evaluate(() => {
180
65
  tr: 'row',
181
66
  ul: 'list'
182
67
  };
183
- const implicitAttributes = {
184
- a: [
185
- {
186
- role: 'link',
187
- attributes: {
188
- href: /./
189
- }
190
- }
191
- ],
192
- area: [
193
- {
194
- role: 'link',
195
- attributes: {
196
- href: /./
197
- }
198
- }
199
- ],
200
- h1: [
201
- {
202
- role: 'heading',
203
- attributes: {
204
- 'aria-level': /^1$/
205
- }
206
- }
207
- ],
208
- h2: [
209
- {
210
- role: 'heading',
211
- attributes: {
212
- 'aria-level': /^2$/
213
- }
214
- },
215
- {
216
- role: 'heading',
217
- attributes: {
218
- 'aria-level': false
219
- }
220
- }
221
- ],
222
- h3: [
223
- {
224
- role: 'heading',
225
- attributes: {
226
- 'aria-level': /^3$/
227
- }
228
- }
229
- ],
230
- h4: [
231
- {
232
- role: 'heading',
233
- attributes: {
234
- 'aria-level': /^4$/
235
- }
236
- }
237
- ],
238
- h5: [
239
- {
240
- role: 'heading',
241
- attributes: {
242
- 'aria-level': /^5$/
243
- }
244
- }
245
- ],
246
- h6: [
247
- {
248
- role: 'heading',
249
- attributes: {
250
- 'aria-level': /^6$/
251
- }
252
- }
253
- ],
254
- input: [
255
- {
256
- role: 'checkbox',
257
- attributes: {
258
- type: /^checkbox$/
259
- }
260
- },
261
- {
262
- role: 'button',
263
- attributes: {
264
- type: /^(?:button|image|reset|submit)$/
265
- }
266
- },
267
- {
268
- role: 'combobox',
269
- attributes: {
270
- type: /^(?:email|search|tel|text|url)$/,
271
- list: true
272
- }
273
- },
274
- {
275
- role: 'combobox',
276
- attributes: {
277
- type: false,
278
- list: true
279
- }
280
- },
281
- {
282
- role: 'radio',
283
- attributes: {
284
- type: /^radio$/
285
- }
286
- },
287
- {
288
- role: 'searchbox',
289
- attributes: {
290
- type: /^search$/,
291
- list: false
292
- }
293
- },
294
- {
295
- role: 'slider',
296
- attributes: {
297
- type: /^range$/
298
- }
299
- },
300
- {
301
- role: 'spinbutton',
302
- attributes: {
303
- type: /^number$/
304
- }
305
- },
306
- {
307
- role: 'textbox',
308
- attributes: {
309
- type: /^(?:email|tel|text|url)$/,
310
- list: false
311
- }
312
- },
313
- {
314
- role: 'textbox',
315
- attributes: {
316
- type: false,
317
- list: false
318
- }
319
- },
320
- {
321
- role: 'checkbox',
322
- attributes: {
323
- type: /^checkbox$/
324
- }
325
- },
326
- {
327
- role: 'checkbox',
328
- attributes: {
329
- type: /^checkbox$/
330
- }
331
- },
332
- ],
333
- img: [
334
- {
335
- role: 'presentation',
336
- attributes: {
337
- alt: /^$/
338
- }
339
- },
340
- {
341
- role: 'img',
342
- attributes: {
343
- alt: /./
344
- }
345
- },
346
- {
347
- role: 'img',
348
- attributes: {
349
- alt: false
350
- }
351
- }
352
- ],
353
- select: [
354
- {
355
- role: 'listbox',
356
- attributes: {
357
- multiple: true
358
- }
359
- },
360
- {
361
- role: 'listbox',
362
- attributes: {
363
- size: /^(?:[2-9]|[1-9]\d+)$/
364
- }
365
- },
366
- {
367
- role: 'combobox',
368
- attributes: {
369
- multiple: false,
370
- size: false
371
- }
372
- },
373
- {
374
- role: 'combobox',
375
- attributes: {
376
- multiple: false,
377
- size: /^1$/
378
- }
379
- }
380
- ]
381
- };
382
- // Array of th and td elements with redundant roles.
383
- const redundantCells = [];
384
- const {body} = document;
385
- // Elements with role attributes.
386
- const roleElements = Array.from(body.querySelectorAll('[role]'));
387
- // th and td elements with redundant roles.
388
- const gridHeaders = Array.from(
389
- body.querySelectorAll('table[role=grid] th, table[role=treegrid] th')
390
- );
391
- const gridCells = Array.from(
392
- body.querySelectorAll('table[role=grid] td, table[role=treegrid] td')
393
- );
394
- const tableHeaders = Array.from(
395
- body.querySelectorAll('table[role=table] th, table:not([role]) th')
396
- );
397
- const tableCells = Array.from(
398
- body.querySelectorAll('table[role=table] td, table:not([role]) td')
399
- );
400
- // Initialized result.
401
- const data = {
402
- roleElements: roleElements.length,
403
- badRoleElements: 0,
404
- redundantRoleElements: 0,
405
- tagNames: {}
406
- };
407
-
408
- // FUNCTIONS
409
-
410
- // Initializes the results.
411
- const dataInit = (data, tagName, role) => {
412
- if (! data.tagNames[tagName]) {
413
- data.tagNames[tagName] = {};
414
- }
415
- if (! data.tagNames[tagName][role]) {
416
- data.tagNames[tagName][role] = {
417
- bad: 0,
418
- redundant: 0
419
- };
420
- }
421
- };
422
- //
423
- const tallyTableRedundancy = (elements, okRoles, tagName) => {
424
- elements.forEach(element => {
425
- const role = element.getAttribute('role');
426
- if (okRoles.includes(role)) {
427
- dataInit(data, tagName, role);
428
- data.redundantRoleElements++;
429
- data.tagNames[tagName][role].redundant++;
430
- redundantCells.push(element);
431
- }
432
- });
433
- };
68
+ const implicitRoles = new Set(Object.values(roleImplications));
434
69
 
435
- // OPERATION
70
+ // FUNCTIONS
436
71
 
437
- // Remove the element-implicit roles from the non-abstract roles.
438
- goodRoles.forEach(role => {
439
- if (badRoles.has(role)) {
440
- goodRoles.delete(role);
441
- }
442
- });
443
- // Identify the table elements with redundant roles.
444
- tallyTableRedundancy(gridHeaders, ['columnheader', 'rowheader', 'gridcell'], 'TH');
445
- tallyTableRedundancy(gridCells, ['gridcell'], 'TD');
446
- tallyTableRedundancy(tableHeaders, ['columnheader', 'rowheader', 'cell'], 'TH');
447
- tallyTableRedundancy(tableCells, ['cell'], 'TD');
448
- // Identify the additional elements with redundant roles and bad roles.
449
- roleElements.filter(element => ! redundantCells.includes(element)).forEach(element => {
450
- const role = element.getAttribute('role');
451
- const tagName = element.tagName;
452
- // If the role is not absolutely valid:
453
- if (! goodRoles.has(role)) {
454
- // If it is bad or redundant:
455
- if (badRoles.has(role)) {
456
- dataInit(data, tagName, role);
457
- const lcTagName = tagName.toLowerCase();
458
- // If it is simply redundant:
459
- if (role === implicitRoles[lcTagName]) {
460
- // Update the results.
461
- data.redundantRoleElements++;
462
- data.tagNames[tagName][role].redundant++;
463
- }
464
- // Otherwise, if it is attributionally redundant:
465
- else if (
466
- implicitAttributes[lcTagName] && implicitAttributes[lcTagName].some(
467
- criterion => role === criterion.role && Object.keys(criterion.attributes).every(
468
- attributeName => {
469
- const rule = criterion.attributes[attributeName];
470
- const exists = element.hasAttribute(attributeName);
471
- const value = exists ? element.getAttribute(attributeName) : null;
472
- if (rule === true) {
473
- return exists;
474
- }
475
- else if (rule === false) {
476
- return ! exists;
477
- }
478
- else {
479
- return rule.test(value);
480
- }
481
- }
482
- )
483
- )
484
- ) {
485
- // Update the results.
486
- data.redundantRoleElements++;
487
- data.tagNames[tagName][role].redundant++;
488
- }
489
- // Otherwise, i.e. if it is absolutely invalid:
490
- else {
491
- // Update the results.
492
- data.badRoleElements++;
493
- data.tagNames[tagName][role].bad++;
494
- }
495
- }
496
- // Otherwise, i.e. if it is absolutely invalid:
497
- else {
498
- // Update the results.
499
- data.badRoleElements++;
500
- dataInit(data, tagName, role);
501
- data.tagNames[tagName][role].bad++;
502
- }
72
+ // Runs the test and returns the result.
73
+ exports.reporter = async (page, withItems) => {
74
+ // Get locators for all elements with explicit roles.
75
+ const all = await init(100, page, '[role]');
76
+ // For each locator:
77
+ for (const loc of all.allLocs) {
78
+ // Get the explicit role of the element.
79
+ const role = await loc.getAttribute('role');
80
+ // If it is implicit:
81
+ if (implicitRoles.has(role)) {
82
+ // Add the locator to the array of violators.
83
+ all.locs.push([loc, role]);
503
84
  }
504
- });
505
- const standardInstances = [];
506
- Object.keys(data.tagNames).forEach(tagName => {
507
- Object.keys(data.tagNames[tagName]).forEach(role => {
508
- const pairTotals = data.tagNames[tagName][role];
509
- const redCount = pairTotals.redundant;
510
- if (redCount) {
511
- standardInstances.push({
512
- ruleID: 'role',
513
- what: `Elements have redundant explicit role ${role}`,
514
- count: redCount,
515
- ordinalSeverity: 1,
516
- tagName,
517
- id: '',
518
- location: {
519
- doc: '',
520
- type: '',
521
- spec: ''
522
- },
523
- excerpt: ''
524
- });
525
- }
526
- const badCount = pairTotals.bad;
527
- if (badCount) {
528
- standardInstances.push({
529
- ruleID: 'role',
530
- what:
531
- `Elements have invalid or native-replaceable explicit role ${role}`,
532
- count: badCount,
533
- ordinalSeverity: 3,
534
- tagName,
535
- id: '',
536
- location: {
537
- doc: '',
538
- type: '',
539
- spec: ''
540
- },
541
- excerpt: ''
542
- });
543
- }
544
- });
545
- });
546
- // Return the result.
547
- return {
548
- data,
549
- totals: [0, data.redundantRoleElements, 0, data.badRoleElements],
550
- standardInstances
551
- };
552
- });
85
+ }
86
+ // Populate and return the result.
87
+ const whats = [
88
+ 'Element has an explicit __param__ role, but it is also an implicit HTML element role',
89
+ 'Elements have roles assigned that are also implicit roles of HTML elements'
90
+ ];
91
+ return await report(withItems, all, 'role', whats, 0);
92
+ };
package/tests/testaro.js CHANGED
@@ -100,7 +100,7 @@ const evalRules = {
100
100
  opFoc: 'operable elements that are not Tab-focusable',
101
101
  pseudoP: 'adjacent br elements suspected of nonsemantically simulating p elements',
102
102
  radioSet: 'radio buttons not grouped into standard field sets',
103
- role: 'invalid and native-replacing explicit roles',
103
+ role: 'native-replacing explicit roles',
104
104
  styleDiff: 'style inconsistencies',
105
105
  tabNav: 'nonstandard keyboard navigation between elements with the tab role',
106
106
  targetSize: 'buttons, inputs, and non-inline links smaller than 44 pixels wide and high',