testaro 22.0.0 → 24.0.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/testaro/bulk.js CHANGED
@@ -19,15 +19,18 @@ exports.reporter = async page => {
19
19
  const visiblesLoc = await page.locator('body :visible');
20
20
  const visibleLocs = await visiblesLoc.all();
21
21
  data.visibleElements = visibleLocs.length;
22
- const count = Math.round(data.visibleElements / 400);
22
+ const severity = Math.min(4, Math.round(data.visibleElements / 400));
23
+ const totals = [0, 0, 0, 0];
24
+ if (severity) {
25
+ totals[severity - 1] = 1;
26
+ }
23
27
  return {
24
28
  data,
25
- totals: [count, 0, 0, 0],
29
+ totals,
26
30
  standardInstances: data.visibleElements < 200 ? [] : [{
27
31
  ruleID: 'bulk',
28
32
  what: 'Page contains a large number of visible elements',
29
- count,
30
- ordinalSeverity: 0,
33
+ ordinalSeverity: severity - 1,
31
34
  tagName: 'HTML',
32
35
  id: '',
33
36
  location: {
package/testaro/focOp.js CHANGED
@@ -2,183 +2,48 @@
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
- }
84
- }
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
- });
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);
132
41
  }
133
42
  }
134
43
  }
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
- }
170
- }
171
- // Reload the page.
172
- try {
173
- await page.reload({timeout: 15000});
174
- }
175
- catch(error) {
176
- console.log('ERROR: page reload timed out');
177
- }
178
- // Return the standard result.
179
- return {
180
- data,
181
- totals,
182
- standardInstances
183
- };
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
+ return await report(withItems, all, 'focOp', whats, 2);
184
49
  };
@@ -0,0 +1,50 @@
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
+ return await report(withItems, all, 'opFoc', whats, 3);
50
+ };
package/tests/testaro.js CHANGED
@@ -3,7 +3,7 @@
3
3
  This test implements the Testaro evaluative rules.
4
4
  */
5
5
 
6
- // CONSTANTS
6
+ // ######## CONSTANTS
7
7
 
8
8
  const evalRules = {
9
9
  allCaps: 'leaf elements with entirely upper-case text longer than 7 characters',
@@ -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',
@@ -72,11 +73,11 @@ exports.reporter = async (page, options) => {
72
73
  && rules.slice(1).every(rule => evalRules[rule] || etcRules[rule])
73
74
  ) {
74
75
  // For each rule invoked:
75
- const realRules = rules[0] === 'y'
76
+ const calledRules = rules[0] === 'y'
76
77
  ? rules.slice(1)
77
78
  : Object.keys(evalRules).filter(ruleID => ! rules.slice(1).includes(ruleID));
78
79
  const testTimes = [];
79
- for (const rule of realRules) {
80
+ for (const rule of calledRules) {
80
81
  // Initialize an argument array.
81
82
  const ruleArgs = [page, withItems];
82
83
  // If the rule has extra arguments:
@@ -93,17 +94,19 @@ exports.reporter = async (page, options) => {
93
94
  console.log(`>>>>>> ${rule} (${what})`);
94
95
  try {
95
96
  const startTime = Date.now();
96
- const report = await require(`../testaro/${rule}`).reporter(... ruleArgs);
97
+ const ruleReport = await require(`../testaro/${rule}`).reporter(... ruleArgs);
98
+ // Add data from the test to the result.
97
99
  const endTime = Date.now();
98
100
  testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
99
- Object.keys(report).forEach(key => {
100
- data.rules[rule][key] = report[key];
101
- if (report.prevented) {
102
- data.preventions.push(rule);
103
- }
101
+ Object.keys(ruleReport).forEach(key => {
102
+ data.rules[rule][key] = ruleReport[key];
104
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
+ }
105
108
  // If testing is to stop after a failure and the page failed the test:
106
- if (stopOnFail && report.totals.some(total => total)) {
109
+ if (stopOnFail && ruleReport.totals.some(total => total)) {
107
110
  // Stop testing.
108
111
  break;
109
112
  }
File without changes