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/README.md +27 -15
- package/actSpecs.js +4 -3
- package/dirWatch.js +1 -1
- package/package.json +1 -1
- package/procs/nav.js +220 -0
- package/procs/operable.js +85 -0
- package/procs/testaro.js +2 -2
- package/procs/visChange.js +1 -0
- package/run.js +494 -684
- package/testaro/bulk.js +7 -4
- package/testaro/focOp.js +33 -168
- package/testaro/opFoc.js +50 -0
- package/tests/testaro.js +14 -11
- /package/{standardize.js → procs/standardize.js} +0 -0
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
|
|
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
|
|
29
|
+
totals,
|
|
26
30
|
standardInstances: data.visibleElements < 200 ? [] : [{
|
|
27
31
|
ruleID: 'bulk',
|
|
28
32
|
what: 'Page contains a large number of visible elements',
|
|
29
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
21
|
-
const {
|
|
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
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
};
|
package/testaro/opFoc.js
ADDED
|
@@ -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: '
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
100
|
-
data.rules[rule][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 &&
|
|
109
|
+
if (stopOnFail && ruleReport.totals.some(total => total)) {
|
|
107
110
|
// Stop testing.
|
|
108
111
|
break;
|
|
109
112
|
}
|
|
File without changes
|