testaro 5.6.2 → 5.7.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Federated accessibility test automation
6
6
 
7
7
  Testaro is a collection of collections of web accessibility tests.
8
8
 
9
- The purpose of Testaro is to provide programmatic access to over 800 accessibility tests defined in several test packages and in Testaro itself.
9
+ The purpose of Testaro is to provide programmatic access to 1228 accessibility tests defined in several test packages and in Testaro itself.
10
10
 
11
11
  ## System requirements
12
12
 
@@ -26,6 +26,9 @@ Testaro includes some of its own accessibility tests. In addition, it performs t
26
26
  - [axe-playwright](https://www.npmjs.com/package/axe-playwright) (Deque Axe-core)
27
27
  - [Tenon](https://tenon.io/documentation/what-tenon-tests.php) (Level Access)
28
28
  - [WAVE API](https://wave.webaim.org/api/) (WebAIM WAVE)
29
+ - [Nu Html Checker](https://github.com/validator/validator)
30
+
31
+ Some of the Testaro tests are derived from tests performed by the [BBC Accessibility Standards Checker](https://github.com/bbc/bbc-a11y).
29
32
 
30
33
  As of this version, the counts of tests in the packages referenced above were:
31
34
  - Alfa: 103
@@ -35,9 +38,10 @@ As of this version, the counts of tests in the packages referenced above were:
35
38
  - HTML CodeSniffer: 98
36
39
  - Tenon: 180
37
40
  - WAVE: 110
41
+ - Nu Html Checker: 147
38
42
  - subtotal: 612
39
- - Testaro tests: 16
40
- - grand total: 1075
43
+ - Testaro tests: 22
44
+ - grand total: 1228
41
45
 
42
46
  ## Code organization
43
47
 
@@ -330,6 +334,10 @@ The changes in `htmlcs/HTMLCS.js` are:
330
334
  > );
331
335
  ```
332
336
 
337
+ ###### BBC Accessibility Standards Checker
338
+
339
+ The BBC Accessibility Standards Checker has obsolete dependencies with security vulnerabilities. Therefore, it is not used as a dependency of Testaro. Instead, 6 of its tests were reimplemented, in some case with revisions, as Testaro tests. They were drawn from the 18 automated tests of the Checker. The other 12 tests were found too duplicative of other tests to justify reimplementation.
340
+
333
341
  ##### Branching
334
342
 
335
343
  An example of a **branching** command is:
package/commands.js CHANGED
@@ -170,6 +170,12 @@ exports.commands = {
170
170
  withItems: [true, 'boolean']
171
171
  }
172
172
  ],
173
+ focVis: [
174
+ 'Perform a focVis test',
175
+ {
176
+ withItems: [true, 'boolean']
177
+ }
178
+ ],
173
179
  hover: [
174
180
  'Perform a hover test',
175
181
  {
@@ -196,6 +202,12 @@ exports.commands = {
196
202
  withItems: [true, 'boolean']
197
203
  }
198
204
  ],
205
+ linkTo: [
206
+ 'Perform a linkTo test',
207
+ {
208
+ withItems: [true, 'boolean']
209
+ }
210
+ ],
199
211
  linkUl: [
200
212
  'Perform a linkUl test',
201
213
  {
@@ -208,6 +220,12 @@ exports.commands = {
208
220
  withItems: [true, 'boolean']
209
221
  }
210
222
  ],
223
+ miniText: [
224
+ 'Perform a miniText test',
225
+ {
226
+ withItems: [true, 'boolean']
227
+ }
228
+ ],
211
229
  motion: [
212
230
  'Perform a motion test',
213
231
  {
@@ -216,6 +234,12 @@ exports.commands = {
216
234
  count: [true, 'number', '', 'count of screen shots to make']
217
235
  }
218
236
  ],
237
+ nonTable: [
238
+ 'Perform a nonTable test',
239
+ {
240
+ withItems: [true, 'boolean']
241
+ }
242
+ ],
219
243
  radioSet: [
220
244
  'Perform a radioSet test',
221
245
  {
@@ -240,6 +264,12 @@ exports.commands = {
240
264
  id: [true, 'string', 'hasLength', 'ID of the requested test instance']
241
265
  }
242
266
  ],
267
+ titledEl: [
268
+ 'Perform a titledEl test',
269
+ {
270
+ withItems: [true, 'boolean']
271
+ }
272
+ ],
243
273
  wave: [
244
274
  'Perform a WebAIM WAVE test',
245
275
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "5.6.2",
3
+ "version": "5.7.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -33,22 +33,29 @@ const tests = {
33
33
  axe: 'Axe',
34
34
  bulk: 'count of visible elements',
35
35
  continuum: 'Level Access Continuum, community edition',
36
+ docType: 'document without a doctype property',
36
37
  embAc: 'active elements embedded in links or buttons',
37
38
  focAll: 'focusable and Tab-focused elements',
38
39
  focInd: 'focus indicators',
39
40
  focOp: 'focusability and operability',
41
+ focVis: 'links that are invisible when focused',
40
42
  hover: 'hover-caused content changes',
41
43
  htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
42
44
  ibm: 'IBM Accessibility Checker',
43
45
  labClash: 'labeling inconsistencies',
46
+ linkTo: 'links without destinations',
44
47
  linkUl: 'adjacent-link underlining',
45
48
  menuNav: 'keyboard navigation between focusable menu items',
49
+ miniText: 'text smaller than 11 pixels',
46
50
  motion: 'motion',
51
+ nonTable: 'table elements used for layout',
52
+ nuVal: 'failures to pass the Nu Html Checker',
47
53
  radioSet: 'fieldset grouping of radio buttons',
48
54
  role: 'roles',
49
55
  styleDiff: 'style inconsistencies',
50
56
  tabNav: 'keyboard navigation between tab elements',
51
57
  tenon: 'Tenon',
58
+ titledEl: 'title attributes on inappropriate elements',
52
59
  wave: 'WAVE',
53
60
  zIndex: 'z indexes'
54
61
  };
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "tp12",
2
+ "id": "tp14",
3
3
  "what": "Alfa, Axe, Continuum, HTML CodeSniffer, IBM, Tenon, WAVE, and 16 custom tests",
4
4
  "strict": true,
5
- "timeLimit": 300,
5
+ "timeLimit": 400,
6
6
  "commands": [
7
7
  {
8
8
  "type": "launch",
@@ -0,0 +1,173 @@
1
+ {
2
+ "id": "tp15",
3
+ "what": "Alfa, Axe, Continuum, HTML CodeSniffer, IBM, Nu Html Checker, Tenon, WAVE, and 22 custom tests",
4
+ "strict": true,
5
+ "timeLimit": 500,
6
+ "commands": [
7
+ {
8
+ "type": "launch",
9
+ "which": "webkit",
10
+ "what": "Webkit browser"
11
+ },
12
+ {
13
+ "type": "url",
14
+ "which": "https://*",
15
+ "what": "any page"
16
+ },
17
+ {
18
+ "type": "tenonRequest",
19
+ "id": "a",
20
+ "withNewContent": true,
21
+ "what": "Tenon API version 2 test request"
22
+ },
23
+ {
24
+ "type": "test",
25
+ "which": "motion",
26
+ "what": "spontaneous change of content; requires webkit",
27
+ "delay": 2500,
28
+ "interval": 2500,
29
+ "count": 5
30
+ },
31
+ {
32
+ "type": "launch",
33
+ "which": "chromium",
34
+ "what": "Chromium browser"
35
+ },
36
+ {
37
+ "type": "url",
38
+ "which": "https://*",
39
+ "what": "any page"
40
+ },
41
+ {
42
+ "type": "test",
43
+ "which": "bulk",
44
+ "what": "count of visible elements"
45
+ },
46
+ {
47
+ "type": "test",
48
+ "which": "embAc",
49
+ "withItems": true,
50
+ "what": "active elements incorrectly embedded in each other"
51
+ },
52
+ {
53
+ "type": "test",
54
+ "which": "focAll",
55
+ "what": "Tab-focusability"
56
+ },
57
+ {
58
+ "type": "test",
59
+ "which": "focInd",
60
+ "revealAll": false,
61
+ "allowedDelay": 250,
62
+ "withItems": true,
63
+ "what": "focus indicators"
64
+ },
65
+ {
66
+ "type": "test",
67
+ "which": "focOp",
68
+ "withItems": true,
69
+ "what": "focusability and operability of elements"
70
+ },
71
+ {
72
+ "type": "test",
73
+ "which": "hover",
74
+ "headSize": 40,
75
+ "headSampleSize": 20,
76
+ "tailSampleSize": 15,
77
+ "withItems": true,
78
+ "what": "hover impacts"
79
+ },
80
+ {
81
+ "type": "test",
82
+ "which": "labClash",
83
+ "withItems": true,
84
+ "what": "unlabeled and mislabeled form controls"
85
+ },
86
+ {
87
+ "type": "test",
88
+ "which": "linkUl",
89
+ "withItems": true,
90
+ "what": "underlining of inline links"
91
+ },
92
+ {
93
+ "type": "test",
94
+ "which": "menuNav",
95
+ "withItems": true,
96
+ "what": "keyboard navigation within true-focus menus"
97
+ },
98
+ {
99
+ "type": "test",
100
+ "which": "radioSet",
101
+ "withItems": true,
102
+ "what": "grouping of radio buttons in fieldsets"
103
+ },
104
+ {
105
+ "type": "test",
106
+ "which": "role",
107
+ "what": "validity and necessity of role assignments"
108
+ },
109
+ {
110
+ "type": "test",
111
+ "which": "styleDiff",
112
+ "withItems": true,
113
+ "what": "style consistency of headings, buttons, and links"
114
+ },
115
+ {
116
+ "type": "test",
117
+ "which": "tabNav",
118
+ "withItems": true,
119
+ "what": "keyboard navigation within tab lists"
120
+ },
121
+ {
122
+ "type": "test",
123
+ "which": "zIndex",
124
+ "withItems": true,
125
+ "what": "elements with non-auto z indexes"
126
+ },
127
+ {
128
+ "type": "test",
129
+ "which": "alfa",
130
+ "what": "Siteimprove alfa"
131
+ },
132
+ {
133
+ "type": "test",
134
+ "which": "axe",
135
+ "detailLevel": 2,
136
+ "rules": [],
137
+ "what": "Axe core, all rules"
138
+ },
139
+ {
140
+ "type": "test",
141
+ "which": "continuum",
142
+ "what": "Continuum"
143
+ },
144
+ {
145
+ "type": "test",
146
+ "which": "htmlcs",
147
+ "what": "HTML CodeSniffer"
148
+ },
149
+ {
150
+ "type": "test",
151
+ "which": "ibm",
152
+ "withItems": true,
153
+ "what": "IBM Accessibility Checker, with page content and again with URL"
154
+ },
155
+ {
156
+ "type": "test",
157
+ "which": "nuVal",
158
+ "what": "Nu Html Checker"
159
+ },
160
+ {
161
+ "type": "test",
162
+ "which": "wave",
163
+ "reportType": 4,
164
+ "what": "WAVE, report-type 4"
165
+ },
166
+ {
167
+ "type": "test",
168
+ "which": "tenon",
169
+ "id": "a",
170
+ "what": "Tenon API version 2 result retrieval"
171
+ }
172
+ ]
173
+ }
@@ -0,0 +1,12 @@
1
+ /*
2
+ docType
3
+ Derived from the bbc-a11y allDocumentsMustHaveAW3cRecommendedDoctype test.
4
+ This test reports a failure to equip the page document with a W3C-recommended doctype.
5
+ */
6
+ exports.reporter = async page => {
7
+ // Identify the visible links without href attributes.
8
+ const docType = await page.evaluate(() => document.doctype);
9
+ return {result: {
10
+ docHasType: Boolean(docType)
11
+ }};
12
+ };
@@ -0,0 +1,29 @@
1
+ /*
2
+ focVis
3
+ Derived from the bbc-a11y elementsMustBeVisibleOnFocus test.
4
+ This test reports links that are off the display when focused.
5
+ */
6
+ exports.reporter = async (page, withItems) => {
7
+ // Identify the initially visible links.
8
+ const badLinks = await page.$$eval('a:visible', links => {
9
+ // FUNCTION DEFINITION START
10
+ // Returns a space-minimized copy of a string.
11
+ const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
12
+ // FUNCTION DEFINITION END
13
+ const badLinks = [];
14
+ links.forEach(link => {
15
+ link.focus();
16
+ if (link.offsetTop + link.offsetHeight <= 0 || link.offsetLeft + link.offsetWidth <= 0) {
17
+ badLinks.push(compact(link.textContent));
18
+ }
19
+ });
20
+ return badLinks;
21
+ });
22
+ const data = {
23
+ totals: badLinks.length
24
+ };
25
+ if (withItems) {
26
+ data.items = badLinks;
27
+ }
28
+ return {result: data};
29
+ };
@@ -0,0 +1,25 @@
1
+ /*
2
+ linkTo
3
+ Derived from the bbc-a11y anchorsMustHaveHrefs test.
4
+ This test reports failures to equip links with destinations.
5
+ */
6
+ exports.reporter = async (page, withItems) => {
7
+ // Identify the visible links without href attributes.
8
+ const badLinkTexts = await page.$$eval(
9
+ 'a:not([href]):visible',
10
+ badLinks => {
11
+ // FUNCTION DEFINITION START
12
+ // Returns a space-minimized copy of a string.
13
+ const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
14
+ // FUNCTION DEFINITION END
15
+ return badLinks.map(link => compact(link.textContent));
16
+ }
17
+ );
18
+ const data = {
19
+ totals: badLinkTexts.length
20
+ };
21
+ if (withItems) {
22
+ data.items = badLinkTexts;
23
+ }
24
+ return {result: data};
25
+ };
@@ -0,0 +1,48 @@
1
+ /*
2
+ miniText
3
+ Derived from the bbc-a11y textCannotBeTooSmall test.
4
+ This test reports text nodes smaller than 11 pixels.
5
+ */
6
+ exports.reporter = async (page, withItems) => {
7
+ // Identify the text nodes smaller than 11 pixels.
8
+ const miniTexts = await page.$$eval(
9
+ 'body *:not(script, style):visible',
10
+ elements => {
11
+ // FUNCTION DEFINITION START
12
+ // Returns a space-minimized copy of a string.
13
+ const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
14
+ // FUNCTION DEFINITION END
15
+ const textParents = new Set();
16
+ const miniTexts = [];
17
+ elements.forEach(element => {
18
+ element.childNodes.forEach(node => {
19
+ if (node.nodeType === 3) {
20
+ const nodeText = compact(node.textContent);
21
+ if (nodeText) {
22
+ textParents.add(element);
23
+ }
24
+ }
25
+ });
26
+ });
27
+ textParents.forEach(textParent => {
28
+ const {fontSize} = window.getComputedStyle(textParent);
29
+ const pixels = Number.parseInt(fontSize);
30
+ if (pixels < 11) {
31
+ textParent.childNodes.forEach(node => {
32
+ if (node.nodeType === 3 && compact(node.textContent)) {
33
+ miniTexts.push(compact(node.textContent));
34
+ }
35
+ });
36
+ }
37
+ });
38
+ return miniTexts;
39
+ }
40
+ );
41
+ const data = {
42
+ totals: miniTexts.length
43
+ };
44
+ if (withItems) {
45
+ data.items = miniTexts;
46
+ }
47
+ return {result: data};
48
+ };
@@ -0,0 +1,55 @@
1
+ /*
2
+ nonTable
3
+ Derived from the bbc-a11y useTablesForData test. Crude heuristics omitted.
4
+ This test reports tables used for layout.
5
+ */
6
+ exports.reporter = async (page, withItems) => {
7
+ // Identify the visible links without href attributes.
8
+ const badTableTexts = await page.$$eval('table', tables => {
9
+ const badTableTexts = [];
10
+ // FUNCTION DEFINITIONS START
11
+ // Returns a space-minimized copy of a string.
12
+ const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
13
+ const addBad = table => {
14
+ badTableTexts.push(compact(table.outerHTML).slice(0, 100));
15
+ };
16
+ // FUNCTION DEFINITIONS END
17
+ tables.forEach(table => {
18
+ const role = table.getAttribute('role');
19
+ if (
20
+ table.caption
21
+ || ['grid', 'treegrid'].includes(role)
22
+ || table.querySelector('col, colgroup, tfoot, thead, th')
23
+ ) {
24
+ return;
25
+ }
26
+ else if (table.querySelector('table')) {
27
+ addBad(table);
28
+ return;
29
+ }
30
+ else if (
31
+ table.querySelectorAll('tr').length === 1
32
+ || Math.max(
33
+ ... Array
34
+ .from(table.querySelectorAll('tr'))
35
+ .map(row => Array.from(row.querySelectorAll('td')).length)
36
+ ) === 1
37
+ ) {
38
+ addBad(table);
39
+ return;
40
+ }
41
+ else if (table.querySelector('object, embed, applet, audio, video')) {
42
+ addBad(table);
43
+ return;
44
+ }
45
+ });
46
+ return badTableTexts;
47
+ });
48
+ const data = {
49
+ totals: badTableTexts.length
50
+ };
51
+ if (withItems) {
52
+ data.items = badTableTexts;
53
+ }
54
+ return {result: data};
55
+ };
package/tests/nuVal.js ADDED
@@ -0,0 +1,46 @@
1
+ /*
2
+ nuVal
3
+ This test subjects a page to the Nu Html Checker.
4
+ */
5
+ const https = require('https');
6
+ exports.reporter = async page => {
7
+ const pageContent = await page.content();
8
+ // Get the data from a Nu validation.
9
+ const data = await new Promise(resolve => {
10
+ const request = https.request(
11
+ {
12
+ host: 'validator.nu',
13
+ path: '/?parser=html&out=json',
14
+ method: 'POST',
15
+ headers: {
16
+ 'User-Agent': 'Mozilla/5.0',
17
+ 'Content-Type': 'text/html; charset=utf-8'
18
+ }
19
+ },
20
+ response => {
21
+ let report = '';
22
+ response.on('data', chunk => {
23
+ report += chunk;
24
+ });
25
+ // When the data arrive:
26
+ response.on('end', async () => {
27
+ try {
28
+ // Delete unnecessary properties.
29
+ const result = JSON.parse(report);
30
+ return resolve(result);
31
+ }
32
+ catch (error) {
33
+ return resolve({
34
+ prevented: true,
35
+ error: error.message,
36
+ report
37
+ });
38
+ }
39
+ });
40
+ }
41
+ );
42
+ request.write(pageContent);
43
+ request.end();
44
+ });
45
+ return {result: data};
46
+ };
package/tests/role.js CHANGED
@@ -2,15 +2,19 @@
2
2
  role
3
3
  This test reports role assignment that violate either an applicable standard or an applicable
4
4
  recommendation from WAI-ARIA. Invalid roles include those that are abstract and thus prohibited
5
- from direct use, and those that are implicit in HTML elements and thus advised against. The math
6
- role has been removed, because of poor adoption and exclusion from HTML5. The img role has
7
- accessibility uses, so is not classified as deprecated. See:
5
+ from direct use, and those that are implicit in HTML elements and thus advised against. Roles
6
+ that explicitly confirm implicit roles are deemed redundant and can be scored as less serious
7
+ than roles that override implicit roles. The math role has been removed, because of poor
8
+ adoption and exclusion from HTML5. The img role has accessibility uses, so is not classified
9
+ as deprecated. See:
8
10
  https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Role_Img
9
11
  https://www.w3.org/TR/html-aria/
10
12
  https://www.w3.org/TR/wai-aria/#roles_categorization
11
13
  */
12
14
  exports.reporter = async page => await page.$eval('body', body => {
15
+
13
16
  // CONSTANTS
17
+
14
18
  const badRoles = new Set([
15
19
  'article',
16
20
  'banner',
@@ -354,7 +358,32 @@ exports.reporter = async page => await page.$eval('body', body => {
354
358
  };
355
359
  // Array of th and td elements with redundant roles.
356
360
  const redundantCells = [];
361
+ // Elements with role attributes.
362
+ const roleElements = Array.from(body.querySelectorAll('[role]'));
363
+ // th and td elements with redundant roles.
364
+ const gridHeaders = Array.from(
365
+ document.body.querySelectorAll('table[role=grid] th, table[role=treegrid] th')
366
+ );
367
+ const gridCells = Array.from(
368
+ document.body.querySelectorAll('table[role=grid] td, table[role=treegrid] td')
369
+ );
370
+ const tableHeaders = Array.from(
371
+ document.body.querySelectorAll('table[role=table] th, table:not([role]) th')
372
+ );
373
+ const tableCells = Array.from(
374
+ document.body.querySelectorAll('table[role=table] td, table:not([role]) td')
375
+ );
376
+ // Initialized result.
377
+ const data = {
378
+ roleElements: roleElements.length,
379
+ badRoleElements: 0,
380
+ redundantRoleElements: 0,
381
+ tagNames: {}
382
+ };
383
+
357
384
  // FUNCTIONS
385
+
386
+ // Initializes the results.
358
387
  const dataInit = (data, tagName, role) => {
359
388
  if (! data.tagNames[tagName]) {
360
389
  data.tagNames[tagName] = {};
@@ -366,6 +395,7 @@ exports.reporter = async page => await page.$eval('body', body => {
366
395
  };
367
396
  }
368
397
  };
398
+ //
369
399
  const tallyTableRedundancy = (elements, okRoles, tagName) => {
370
400
  elements.forEach(element => {
371
401
  const role = element.getAttribute('role');
@@ -377,35 +407,16 @@ exports.reporter = async page => await page.$eval('body', body => {
377
407
  }
378
408
  });
379
409
  };
410
+
380
411
  // OPERATION
412
+
381
413
  // Remove the deprecated roles from the non-abstract roles.
382
414
  goodRoles.forEach(role => {
383
415
  if (badRoles.has(role)) {
384
416
  goodRoles.delete(role);
385
417
  }
386
418
  });
387
- // Identify all elements with role attributes.
388
- const roleElements = Array.from(body.querySelectorAll('[role]'));
389
- // Initialize the result.
390
- const data = {
391
- roleElements: roleElements.length,
392
- badRoleElements: 0,
393
- redundantRoleElements: 0,
394
- tagNames: {}
395
- };
396
- // Identify the th and td elements with redundant roles.
397
- const gridHeaders = Array.from(
398
- document.body.querySelectorAll('table[role=grid] th, table[role=treegrid] th')
399
- );
400
- const gridCells = Array.from(
401
- document.body.querySelectorAll('table[role=grid] td, table[role=treegrid] td')
402
- );
403
- const tableHeaders = Array.from(
404
- document.body.querySelectorAll('table[role=table] th, table:not([role]) th')
405
- );
406
- const tableCells = Array.from(
407
- document.body.querySelectorAll('table[role=table] td, table:not([role]) td')
408
- );
419
+ // Identify the table elements with redundant roles.
409
420
  tallyTableRedundancy(gridHeaders, ['columnheader', 'rowheader', 'gridcell'], 'TH');
410
421
  tallyTableRedundancy(gridCells, ['gridcell'], 'TD');
411
422
  tallyTableRedundancy(tableHeaders, ['columnheader', 'rowheader', 'cell'], 'TH');
@@ -422,6 +433,7 @@ exports.reporter = async page => await page.$eval('body', body => {
422
433
  const lcTagName = tagName.toLowerCase();
423
434
  // If it is simply redundant:
424
435
  if (role === implicitRoles[lcTagName]) {
436
+ // Update the results.
425
437
  data.redundantRoleElements++;
426
438
  data.tagNames[tagName][role].redundant++;
427
439
  }
@@ -446,18 +458,20 @@ exports.reporter = async page => await page.$eval('body', body => {
446
458
  )
447
459
  )
448
460
  ) {
461
+ // Update the results.
449
462
  data.redundantRoleElements++;
450
463
  data.tagNames[tagName][role].redundant++;
451
464
  }
452
465
  // Otherwise, i.e. if it is absolutely invalid:
453
466
  else {
467
+ // Update the results.
454
468
  data.badRoleElements++;
455
469
  data.tagNames[tagName][role].bad++;
456
470
  }
457
471
  }
458
472
  // Otherwise, i.e. if it is absolutely invalid:
459
473
  else {
460
- // Add the facts to the result.
474
+ // Update the results.
461
475
  data.badRoleElements++;
462
476
  dataInit(data, tagName, role);
463
477
  data.tagNames[tagName][role].bad++;
@@ -0,0 +1,29 @@
1
+ /*
2
+ titledEl
3
+ Derived from the bbc-a11y titleAttributesOnlyOnInputs test.
4
+ This test reports title attributes on inappropriate elements.
5
+ */
6
+ exports.reporter = async (page, withItems) => {
7
+ // Identify the inappropriate elements with title attributes.
8
+ const badTitleElements = await page.$$eval(
9
+ '[title]:not(input, button, textarea, select, iframe):visible',
10
+ badTitleElements => {
11
+ // FUNCTION DEFINITION START
12
+ // Returns a space-minimized copy of a string.
13
+ const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
14
+ // FUNCTION DEFINITION END
15
+ return badTitleElements.map(element => ({
16
+ tagName: element.tagName,
17
+ text: compact(element.textContent),
18
+ title: compact(element.title)
19
+ }));
20
+ }
21
+ );
22
+ const data = {
23
+ totals: badTitleElements.length
24
+ };
25
+ if (withItems) {
26
+ data.items = badTitleElements;
27
+ }
28
+ return {result: data};
29
+ };
package/tests/wave.js CHANGED
@@ -41,7 +41,7 @@ exports.reporter = async (page, reportType) => {
41
41
  const {guidelines} = issueDoc;
42
42
  items[issueName].wcag = guidelines;
43
43
  });
44
- })
44
+ });
45
45
  return resolve(result);
46
46
  }
47
47
  catch (error) {