testaro 23.0.0 → 24.0.1

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": "23.0.0",
3
+ "version": "24.0.1",
4
4
  "description": "Run 920 web accessibility tests from 9 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,85 @@
1
+ /*
2
+ operable
3
+
4
+ Returns whether the element of a locator is operable., i.e. it has a non-inherited pointer cursor
5
+ and is not a 'LABEL' element, has an operable tag name, has an interactive explicit role, or has
6
+ an 'onclick' attribute.
7
+ */
8
+
9
+ // ########## FUNCTIONS
10
+
11
+ // Gets whether an element is operable.
12
+ exports.isOperable = async loc => {
13
+ // Get whether and, if so, how the element is operable.
14
+ const operabilities = await loc.evaluate(el => {
15
+ // Operable tag names.
16
+ const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA']);
17
+ // Operable roles.
18
+ const opRoles = new Set([
19
+ 'button',
20
+ 'checkbox',
21
+ 'combobox',
22
+ 'composite',
23
+ 'grid',
24
+ 'gridcell',
25
+ 'input',
26
+ 'link',
27
+ 'listbox',
28
+ 'menu',
29
+ 'menubar',
30
+ 'menuitem',
31
+ 'menuitemcheckbox',
32
+ 'option',
33
+ 'radio',
34
+ 'radiogroup',
35
+ 'scrollbar',
36
+ 'searchbox',
37
+ 'select',
38
+ 'slider',
39
+ 'spinbutton',
40
+ 'switch',
41
+ 'tab',
42
+ 'tablist',
43
+ 'textbox',
44
+ 'tree',
45
+ 'treegrid',
46
+ 'treeitem',
47
+ 'widget',
48
+ ]);
49
+ // Initialize the operabilities.
50
+ const opHow = [];
51
+ // If the element is not a label and has a non-inherited pointer cursor:
52
+ let hasPointer = false;
53
+ if (el.tagName !== 'LABEL') {
54
+ const styleDec = window.getComputedStyle(el);
55
+ hasPointer = styleDec.cursor === 'pointer';
56
+ if (hasPointer) {
57
+ el.parentElement.style.cursor = 'default';
58
+ hasPointer = styleDec.cursor === 'pointer';
59
+ }
60
+ }
61
+ if (hasPointer) {
62
+ // Add this to the operabilities.
63
+ opHow.push('pointer cursor');
64
+ }
65
+ // If the element is clickable:
66
+ if (el.onClick) {
67
+ // Add this to the operabilities.
68
+ opHow.push('click listener');
69
+ }
70
+ // If the element has an operable explicit role:
71
+ const role = el.getAttribute('role');
72
+ if (opRoles.has(role)) {
73
+ // Add this to the operabilities.
74
+ opHow.push(`role ${role}`);
75
+ }
76
+ // If the element has an operable type:
77
+ const tagName = el.tagName;
78
+ if (opTags.has(tagName)) {
79
+ // Add this to the operabilities.
80
+ opHow.push(`tag name ${tagName}`);
81
+ }
82
+ return opHow;
83
+ });
84
+ return operabilities;
85
+ };
package/testaro/focOp.js CHANGED
@@ -2,183 +2,56 @@
2
2
  focOp
3
3
  Related to Tenon rule 190.
4
4
 
5
- This test reports discrepancies between Tab-focusability and operability. The standard
6
- practice is to make focusable elements operable and vice versa. If focusable elements are not
7
- operable, users are likely to be surprised that nothing happens when they try to operate such
8
- elements. If operable elements are not focusable, users depending on keyboard navigation are
9
- prevented from operating those elements. The test considers an element operable if it has a
10
- non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name ('A',
11
- 'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA'), has an interactive explicit role (button,
12
- link, checkbox, switch, input, textbox, searchbox, combobox, option, treeitem, radio, slider,
13
- spinbutton, menuitem, menuitemcheckbox, composite, grid, select, listbox, menu, menubar, tree,
14
- tablist, tab, gridcell, radiogroup, treegrid, widget, or scrollbar), or has an 'onclick'
15
- attribute. The test considers an element Tab-focusable if its tabIndex property has the value 0.
5
+ This test reports Tab-focusable elements that are not operable. The standard practice is to make
6
+ focusable elements operable. If focusable elements are not operable, users are likely to be
7
+ surprised that nothing happens when they try to operate such elements. The test considers an
8
+ element operable if it has a non-inherited pointer cursor and is not a 'LABEL' element, has an
9
+ operable tag name, has an interactive explicit role, or has an 'onclick' attribute. The test
10
+ considers an element Tab-focusable if its tabIndex property has the value 0.
16
11
  */
17
12
 
18
13
  // ########## IMPORTS
19
14
 
20
- // Module to get locator data.
21
- const {getLocatorData} = require('../procs/getLocatorData');
15
+ // Module to perform common operations.
16
+ const {init, report} = require('../procs/testaro');
17
+ // Module to get operabilities.
18
+ const {isOperable} = require('../procs/operable');
22
19
 
23
20
  // ########## FUNCTIONS
24
21
 
22
+ // Runs the test and returns the result.
25
23
  exports.reporter = async (page, withItems) => {
26
- // Initialize the standard result.
27
- const data = {};
28
- const totals = [0, 0, 0, 0];
29
- const standardInstances = [];
30
- // Identify the operable tag names.
31
- const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA']);
32
- // Identify the operable roles.
33
- const opRoles = new Set([
34
- 'button',
35
- 'checkbox',
36
- 'combobox',
37
- 'composite',
38
- 'grid',
39
- 'gridcell',
40
- 'input',
41
- 'link',
42
- 'listbox',
43
- 'menu',
44
- 'menubar',
45
- 'menuitem',
46
- 'menuitemcheckbox',
47
- 'option',
48
- 'radio',
49
- 'radiogroup',
50
- 'scrollbar',
51
- 'searchbox',
52
- 'select',
53
- 'slider',
54
- 'spinbutton',
55
- 'switch',
56
- 'tab',
57
- 'tablist',
58
- 'textbox',
59
- 'tree',
60
- 'treegrid',
61
- 'treeitem',
62
- 'widget',
63
- ]);
64
- // Get a locator for all body elements.
65
- const locAll = page.locator('body *');
66
- const locsAll = await locAll.all();
67
- // For each of them:
68
- for (const loc of locsAll) {
69
- // Get data on it.
70
- const focOpData = await loc.evaluate(element => {
71
- // Tab index.
72
- const {tabIndex} = element;
73
- // Cursor.
74
- let hasPointer = false;
75
- if (element.tagName !== 'LABEL') {
76
- const styleDec = window.getComputedStyle(element);
77
- hasPointer = styleDec.cursor === 'pointer';
78
- // If the cursor is a pointer:
79
- if (hasPointer) {
80
- // Disregard this if the only reason is inheritance.
81
- element.parentElement.style.cursor = 'default';
82
- hasPointer = styleDec.cursor === 'pointer';
83
- }
24
+ // Initialize the locators and result.
25
+ const all = await init(page, 'body *');
26
+ all.result.data.focusableCount = 0;
27
+ // For each locator:
28
+ for (const loc of all.allLocs) {
29
+ // Get whether its element is focusable.
30
+ const isFocusable = await loc.evaluate(el => el.tabIndex === 0);
31
+ // If it is:
32
+ if (isFocusable) {
33
+ // Add this to the report.
34
+ all.result.data.focusableCount++;
35
+ // Get whether it is operable.
36
+ const howOperable = await isOperable(loc);
37
+ // If it is not:
38
+ if (! howOperable.length) {
39
+ // Add the locator to the array of violators.
40
+ all.locs.push(loc);
84
41
  }
85
- const {tagName} = element;
86
- return {
87
- tabIndex,
88
- hasPointer,
89
- tagName
90
- };
91
- });
92
- focOpData.onClick = await loc.getAttribute('onclick') !== null;
93
- focOpData.role = await loc.getAttribute('role') || '';
94
- focOpData.isFocusable = focOpData.tabIndex === 0;
95
- focOpData.isOperable = focOpData.hasPointer
96
- || opTags.has(focOpData.tagName)
97
- || focOpData.onClick
98
- || opRoles.has(focOpData.role);
99
- // If it is focusable or operable but not both:
100
- if (focOpData.isFocusable !== focOpData.isOperable) {
101
- // Get more data on it.
102
- const elData = await getLocatorData(loc);
103
- // Add to the standard result.
104
- const howOperable = [];
105
- if (opTags.has(focOpData.tagName)) {
106
- howOperable.push(`tag name ${focOpData.tagName}`);
107
- }
108
- if (focOpData.hasPointer) {
109
- howOperable.push('pointer cursor');
110
- }
111
- if (focOpData.onClick) {
112
- howOperable.push('click listener');
113
- }
114
- if (opRoles.has(focOpData.role)) {
115
- howOperable.push(`role ${focOpData.role}`);
116
- }
117
- const gripe = focOpData.isFocusable
118
- ? 'Tab-focusable but not operable'
119
- : `operable (${howOperable.join(', ')}) but not Tab-focusable`;
120
- const ordinalSeverity = focOpData.isFocusable ? 2 : 3;
121
- totals[ordinalSeverity]++;
122
- if (withItems) {
123
- standardInstances.push({
124
- ruleID: 'focOp',
125
- what: `Element is ${gripe}`,
126
- ordinalSeverity,
127
- tagName: elData.tagName,
128
- id: elData.id,
129
- location: elData.location,
130
- excerpt: elData.excerpt
131
- });
132
- }
133
- }
134
- }
135
- // If itemization is not required:
136
- if (! withItems) {
137
- // Add summary instances to the standard result.
138
- if (totals[2]) {
139
- standardInstances.push({
140
- ruleID: 'focOp',
141
- what: 'Tab-focusable elements are inoperable',
142
- count: totals[2],
143
- ordinalSeverity: 2,
144
- tagName: '',
145
- id: '',
146
- location: {
147
- doc: '',
148
- type: '',
149
- spec: ''
150
- },
151
- excerpt: ''
152
- });
153
- }
154
- if (totals[3]) {
155
- standardInstances.push({
156
- ruleID: 'focOp',
157
- what: 'Operable elements are not Tab-focusable',
158
- count: totals[3],
159
- ordinalSeverity: 3,
160
- tagName: '',
161
- id: '',
162
- location: {
163
- doc: '',
164
- type: '',
165
- spec: ''
166
- },
167
- excerpt: ''
168
- });
169
42
  }
170
43
  }
171
- // Reload the page.
44
+ // Populate and return the result.
45
+ const whats = [
46
+ 'Element is Tab-focusable but not operable', 'Elements are Tab-focusable but not operable'
47
+ ];
48
+ const testReport = await report(withItems, all, 'focOp', whats, 2);
49
+ // Reload the page, because isOperable() modified it.
172
50
  try {
173
51
  await page.reload({timeout: 15000});
174
52
  }
175
53
  catch(error) {
176
54
  console.log('ERROR: page reload timed out');
177
55
  }
178
- // Return the standard result.
179
- return {
180
- data,
181
- totals,
182
- standardInstances
183
- };
56
+ return testReport;
184
57
  };
@@ -0,0 +1,58 @@
1
+ /*
2
+ opFoc
3
+ Related to Tenon rule 190.
4
+
5
+ This test reports operable elements that are not Tab-focusable. The standard practice is to make
6
+ operable elements focusable. If operable elements are not focusable, users who navigate with a
7
+ keyboard are prevented from operating those elements. The test considers an element operable if
8
+ it has a non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name, has
9
+ an interactive explicit role, or has an 'onclick' attribute. The test considers an element
10
+ Tab-focusable if its tabIndex property has the value 0.
11
+ */
12
+
13
+ // ########## IMPORTS
14
+
15
+ // Module to perform common operations.
16
+ const {init, report} = require('../procs/testaro');
17
+ // Module to get operabilities.
18
+ const {isOperable} = require('../procs/operable');
19
+
20
+ // ########## FUNCTIONS
21
+
22
+ // Runs the test and returns the result.
23
+ exports.reporter = async (page, withItems) => {
24
+ // Initialize the locators and result.
25
+ const all = await init(page, 'body *');
26
+ all.result.data.operableCount = 0;
27
+ // For each locator:
28
+ for (const loc of all.allLocs) {
29
+ // Get whether and, if so, how its element is operable.
30
+ const operabilities = await isOperable(loc);
31
+ // If it is:
32
+ if (operabilities.length) {
33
+ // Add this to the report.
34
+ all.result.data.operableCount++;
35
+ // Get whether it is focusable.
36
+ const isFocusable = await loc.evaluate(el => el.tabIndex === 0);
37
+ // If it is not:
38
+ if (! isFocusable) {
39
+ // Add the locator to the array of violators.
40
+ all.locs.push([loc, operabilities.join(', ')]);
41
+ }
42
+ }
43
+ }
44
+ // Populate and return the result.
45
+ const whats = [
46
+ 'Element is operable (__param__) but not Tab-focusable',
47
+ 'Elements are operable but not Tab-focusable'
48
+ ];
49
+ const testReport = await report(withItems, all, 'opFoc', whats, 3);
50
+ // Reload the page, because isOperable() modified it.
51
+ try {
52
+ await page.reload({timeout: 15000});
53
+ }
54
+ catch(error) {
55
+ console.log('ERROR: page reload timed out');
56
+ }
57
+ return testReport;
58
+ };
package/tests/testaro.js CHANGED
@@ -19,7 +19,7 @@ const evalRules = {
19
19
  filter: 'filter styles on elements',
20
20
  focAll: 'discrepancies between focusable and Tab-focused elements',
21
21
  focInd: 'missing and nonstandard focus indicators',
22
- focOp: 'discrepancies between focusability and operability',
22
+ focOp: 'Tab-focusable elements that are not operable',
23
23
  focVis: 'links that are invisible when focused',
24
24
  headEl: 'invalid elements within the head',
25
25
  headingAmb: 'same-level sibling headings with identical texts',
@@ -36,6 +36,7 @@ const evalRules = {
36
36
  miniText: 'text smaller than 11 pixels',
37
37
  motion: 'motion without user request',
38
38
  nonTable: 'table elements used for layout',
39
+ opFoc: 'Operable elements that are not Tab-focusable',
39
40
  pseudoP: 'adjacent br elements suspected of nonsemantically simulating p elements',
40
41
  radioSet: 'radio buttons not grouped into standard field sets',
41
42
  role: 'invalid and native-replacing explicit roles',
@@ -99,11 +100,11 @@ exports.reporter = async (page, options) => {
99
100
  testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
100
101
  Object.keys(ruleReport).forEach(key => {
101
102
  data.rules[rule][key] = ruleReport[key];
102
- data.rules[rule].totals = data.rules[rule].totals.map(total => Math.round(total));
103
- if (ruleReport.prevented) {
104
- data.preventions.push(rule);
105
- }
106
103
  });
104
+ data.rules[rule].totals = data.rules[rule].totals.map(total => Math.round(total));
105
+ if (ruleReport.prevented) {
106
+ data.preventions.push(rule);
107
+ }
107
108
  // If testing is to stop after a failure and the page failed the test:
108
109
  if (stopOnFail && ruleReport.totals.some(total => total)) {
109
110
  // Stop testing.