testaro 5.6.4 → 5.7.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/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 1075 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.4",
3
+ "version": "5.7.2",
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,5 +1,5 @@
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
5
  "timeLimit": 400,
@@ -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,64 @@
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, reject) => {
10
+ try {
11
+ const request = https.request(
12
+ {
13
+ // Alternatives (more timeout-prone): host=validator.nu; path=/?parser=html@out=json
14
+ host: 'validator.w3.org',
15
+ path: '/nu/?parser=html&out=json',
16
+ method: 'POST',
17
+ headers: {
18
+ 'User-Agent': 'Mozilla/5.0',
19
+ 'Content-Type': 'text/html; charset=utf-8'
20
+ }
21
+ },
22
+ response => {
23
+ let report = '';
24
+ response.on('data', chunk => {
25
+ report += chunk;
26
+ });
27
+ // When the data arrive:
28
+ response.on('end', async () => {
29
+ try {
30
+ // Delete unnecessary properties.
31
+ const result = JSON.parse(report);
32
+ return resolve(result);
33
+ }
34
+ catch (error) {
35
+ console.log(`Validation failed (${error.message})`);
36
+ return resolve({
37
+ prevented: true,
38
+ error: error.message,
39
+ report
40
+ });
41
+ }
42
+ });
43
+ }
44
+ );
45
+ request.write(pageContent);
46
+ request.end();
47
+ request.on('error', error => {
48
+ console.log(error.message);
49
+ return reject({
50
+ prevented: true,
51
+ error: error.message
52
+ });
53
+ });
54
+ }
55
+ catch(error) {
56
+ console.log(error.message);
57
+ return reject({
58
+ prevented: true,
59
+ error: error.message
60
+ });
61
+ }
62
+ });
63
+ return {result: data};
64
+ };
@@ -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) {