testaro 1.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/LICENSE +21 -0
- package/README.md +502 -0
- package/aceconfig.js +7 -0
- package/commands.js +249 -0
- package/index.js +1248 -0
- package/package.json +39 -0
- package/procs/score/asp09.js +555 -0
- package/procs/test/allText.js +76 -0
- package/procs/test/allVis.js +17 -0
- package/procs/test/linksByType.js +90 -0
- package/procs/test/textOf.txt +73 -0
- package/scoring/correlation.js +74 -0
- package/scoring/correlations.json +327 -0
- package/scoring/data.json +26021 -0
- package/scoring/dupCounts.js +39 -0
- package/scoring/dupCounts.json +112 -0
- package/scoring/duplications.json +253 -0
- package/scoring/issues.json +304 -0
- package/scoring/packageData.js +171 -0
- package/scoring/packageIssues.js +34 -0
- package/scoring/rulesetData.json +15 -0
- package/tests/aatt.js +64 -0
- package/tests/alfa.js +107 -0
- package/tests/axe.js +109 -0
- package/tests/bulk.js +21 -0
- package/tests/embAc.js +36 -0
- package/tests/focAll.js +62 -0
- package/tests/focInd.js +99 -0
- package/tests/focOp.js +132 -0
- package/tests/hover.js +195 -0
- package/tests/ibm.js +89 -0
- package/tests/labClash.js +157 -0
- package/tests/linkUl.js +65 -0
- package/tests/menuNav.js +254 -0
- package/tests/motion.js +115 -0
- package/tests/radioSet.js +87 -0
- package/tests/role.js +164 -0
- package/tests/styleDiff.js +146 -0
- package/tests/tabNav.js +282 -0
- package/tests/wave.js +44 -0
- package/tests/zIndex.js +49 -0
- package/validation/batches/sample.json +13 -0
- package/validation/executors/sample.js +11 -0
- package/validation/scripts/app/sample.json +21 -0
- package/validation/scripts/test/bulk.json +39 -0
- package/validation/scripts/test/embAc.json +45 -0
- package/validation/scripts/test/focAll.json +59 -0
- package/validation/scripts/test/focInd.json +55 -0
- package/validation/scripts/test/focOp.json +53 -0
- package/validation/scripts/test/hover.json +47 -0
- package/validation/scripts/test/labClash.json +43 -0
- package/validation/scripts/test/linkUl.json +62 -0
- package/validation/scripts/test/menuNav.json +97 -0
- package/validation/scripts/test/motion.json +53 -0
- package/validation/scripts/test/radioSet.json +43 -0
- package/validation/scripts/test/role.json +42 -0
- package/validation/scripts/test/styleDiff.json +61 -0
- package/validation/scripts/test/tabNav.json +97 -0
- package/validation/scripts/test/zIndex.json +40 -0
- package/validation/targets/bulk/bad.html +48 -0
- package/validation/targets/bulk/good.html +15 -0
- package/validation/targets/embAc/bad.html +21 -0
- package/validation/targets/embAc/good.html +15 -0
- package/validation/targets/focAll/good.html +15 -0
- package/validation/targets/focAll/less.html +15 -0
- package/validation/targets/focAll/more.html +16 -0
- package/validation/targets/focInd/bad.html +31 -0
- package/validation/targets/focInd/good.html +22 -0
- package/validation/targets/focOp/bad.html +18 -0
- package/validation/targets/focOp/good.html +15 -0
- package/validation/targets/hover/bad.html +19 -0
- package/validation/targets/hover/good.html +15 -0
- package/validation/targets/labClash/bad.html +20 -0
- package/validation/targets/labClash/good.html +18 -0
- package/validation/targets/linkUl/bad.html +16 -0
- package/validation/targets/linkUl/good.html +30 -0
- package/validation/targets/linkUl/na.html +20 -0
- package/validation/targets/menuNav/bad.html +106 -0
- package/validation/targets/menuNav/bad.js +348 -0
- package/validation/targets/menuNav/good.html +106 -0
- package/validation/targets/menuNav/good.js +365 -0
- package/validation/targets/menuNav/style.css +22 -0
- package/validation/targets/motion/bad.css +15 -0
- package/validation/targets/motion/bad.html +16 -0
- package/validation/targets/motion/good.html +15 -0
- package/validation/targets/radioSet/bad.html +34 -0
- package/validation/targets/radioSet/good.html +27 -0
- package/validation/targets/role/bad.html +26 -0
- package/validation/targets/role/good.html +22 -0
- package/validation/targets/styleDiff/bad.html +35 -0
- package/validation/targets/styleDiff/good.html +36 -0
- package/validation/targets/tabNav/bad.html +51 -0
- package/validation/targets/tabNav/bad.js +35 -0
- package/validation/targets/tabNav/good.html +53 -0
- package/validation/targets/tabNav/good.js +83 -0
- package/validation/targets/tabNav/goodMoz.js +206 -0
- package/validation/targets/tabNav/style.css +34 -0
- package/validation/targets/zIndex/bad.html +17 -0
- package/validation/targets/zIndex/good.html +15 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
styleDiff
|
|
3
|
+
This test reports style differences among links, buttons, and headings. It assumes
|
|
4
|
+
that an accessible page employs few or only one style for inline links, and likewise
|
|
5
|
+
for non-inline links, buttons, and headings at each level. The test considers only
|
|
6
|
+
particular style properties, listed in the 'mainStyles' and 'headingStyles' arrays.
|
|
7
|
+
*/
|
|
8
|
+
exports.reporter = async (page, withItems) => {
|
|
9
|
+
// Get an object with arrays of block and inline links as properties.
|
|
10
|
+
const linkTypes = await require('../procs/test/linksByType').linksByType(page);
|
|
11
|
+
return await page.$eval('body', (body, args) => {
|
|
12
|
+
const withItems = args[0];
|
|
13
|
+
const linkTypes = args[1];
|
|
14
|
+
// Initialize the data to be returned.
|
|
15
|
+
const data = {totals: {}};
|
|
16
|
+
if (withItems) {
|
|
17
|
+
data.items = {};
|
|
18
|
+
}
|
|
19
|
+
// Identify the settable style properties to be compared for all tag names.
|
|
20
|
+
const mainStyles = [
|
|
21
|
+
'borderStyle',
|
|
22
|
+
'borderWidth',
|
|
23
|
+
'fontStyle',
|
|
24
|
+
'fontWeight',
|
|
25
|
+
'lineHeight',
|
|
26
|
+
'maxHeight',
|
|
27
|
+
'maxWidth',
|
|
28
|
+
'minHeight',
|
|
29
|
+
'minWidth',
|
|
30
|
+
'opacity',
|
|
31
|
+
'outlineOffset',
|
|
32
|
+
'outlineStyle',
|
|
33
|
+
'outlineWidth',
|
|
34
|
+
'textDecorationLine',
|
|
35
|
+
'textDecorationStyle',
|
|
36
|
+
'textDecorationThickness'
|
|
37
|
+
];
|
|
38
|
+
// Identify those for headings.
|
|
39
|
+
const headingStyles = [
|
|
40
|
+
'fontSize'
|
|
41
|
+
];
|
|
42
|
+
// Identify the heading tag names to be analyzed.
|
|
43
|
+
const headingNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
44
|
+
// Identify the other nonlink tag names to be analyzed.
|
|
45
|
+
const otherNames = ['button'];
|
|
46
|
+
// Initialize an object of elements to be analyzed.
|
|
47
|
+
const elementClasses = {
|
|
48
|
+
headings: {},
|
|
49
|
+
other: {
|
|
50
|
+
aInline: linkTypes.inline,
|
|
51
|
+
aBlock: linkTypes.block
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// For each heading tag name:
|
|
55
|
+
headingNames.forEach(tagName => {
|
|
56
|
+
// Add its elements to the object.
|
|
57
|
+
elementClasses.headings[tagName] = Array.from(body.getElementsByTagName(tagName));
|
|
58
|
+
});
|
|
59
|
+
// For each other tag name:
|
|
60
|
+
otherNames.forEach(tagName => {
|
|
61
|
+
// Add its elements to the object.
|
|
62
|
+
elementClasses.other[tagName] = Array.from(body.getElementsByTagName(tagName));
|
|
63
|
+
});
|
|
64
|
+
// For each element superclass:
|
|
65
|
+
['headings', 'other'].forEach(superClass => {
|
|
66
|
+
// For each class in the superclass:
|
|
67
|
+
Object.keys(elementClasses[superClass]).forEach(tagName => {
|
|
68
|
+
const elements = elementClasses[superClass][tagName];
|
|
69
|
+
const elementCount = elements.length;
|
|
70
|
+
// If there are any:
|
|
71
|
+
if (elementCount) {
|
|
72
|
+
const styleProps = {};
|
|
73
|
+
const styleTexts = {};
|
|
74
|
+
if (withItems) {
|
|
75
|
+
if (! data.items[tagName]) {
|
|
76
|
+
data.items[tagName] = {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// For each of them:
|
|
80
|
+
elements.forEach(element => {
|
|
81
|
+
// Get its values on the style properties to be compared.
|
|
82
|
+
const styleDec = window.getComputedStyle(element);
|
|
83
|
+
const style = {};
|
|
84
|
+
// Identify the styles to be compared.
|
|
85
|
+
const styles = mainStyles;
|
|
86
|
+
if (superClass === 'headings') {
|
|
87
|
+
styles.push(...headingStyles);
|
|
88
|
+
}
|
|
89
|
+
styles.forEach(styleName => {
|
|
90
|
+
style[styleName] = styleDec[styleName];
|
|
91
|
+
});
|
|
92
|
+
// Get a text representation of the style.
|
|
93
|
+
const styleText = JSON.stringify(style);
|
|
94
|
+
// Increment the total of elements with that style declaration.
|
|
95
|
+
styleTexts[styleText] = ++styleTexts[styleText] || 1;
|
|
96
|
+
// If details are required:
|
|
97
|
+
if (withItems) {
|
|
98
|
+
// For each style property:
|
|
99
|
+
styles.forEach(styleName => {
|
|
100
|
+
const styleValue = style[styleName];
|
|
101
|
+
// Increment the total of elements with the same value on it as the element.
|
|
102
|
+
if (styleProps[styleName]) {
|
|
103
|
+
styleProps[styleName][styleValue] = ++styleProps[styleName][styleValue] || 1;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
styleProps[styleName] = {[styleValue]: 1};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// Add the total to the result.
|
|
112
|
+
data.totals[tagName] = {total: elementCount};
|
|
113
|
+
const styleCounts = Object.values(styleTexts);
|
|
114
|
+
// If the elements in the element class differ in style:
|
|
115
|
+
if (styleCounts.length > 1) {
|
|
116
|
+
// Add the distribution of its style counts to the result.
|
|
117
|
+
data.totals[tagName].subtotals = styleCounts.sort((a, b) => b - a);
|
|
118
|
+
}
|
|
119
|
+
// If details are required:
|
|
120
|
+
if (withItems) {
|
|
121
|
+
// For each style property:
|
|
122
|
+
Object.keys(styleProps).forEach(styleProp => {
|
|
123
|
+
// Ignore it if all the elements have the same value.
|
|
124
|
+
if (Object.keys(styleProps[styleProp]).length === 1) {
|
|
125
|
+
delete styleProps[styleProp];
|
|
126
|
+
}
|
|
127
|
+
// Otherwise:
|
|
128
|
+
else {
|
|
129
|
+
if (! data.items[tagName][styleProp]) {
|
|
130
|
+
data.items[tagName][styleProp] = {};
|
|
131
|
+
}
|
|
132
|
+
// Sort the values in order of decreasing count.
|
|
133
|
+
const sortedEntries = Object.entries(styleProps[styleProp]).sort((a, b) => b[1] - a[1]);
|
|
134
|
+
sortedEntries.forEach(entry => {
|
|
135
|
+
const propData = data.items[tagName][styleProp];
|
|
136
|
+
propData[entry[0]] = (propData[entry[0]] || 0) + entry[1];
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
return {result: data};
|
|
145
|
+
}, [withItems, linkTypes]);
|
|
146
|
+
};
|
package/tests/tabNav.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/*
|
|
2
|
+
tabNav
|
|
3
|
+
This test reports nonstandard keyboard navigation among tab elements in tab lists.
|
|
4
|
+
Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Initialize a report.
|
|
8
|
+
const data = {
|
|
9
|
+
totals: {
|
|
10
|
+
navigations: {
|
|
11
|
+
all: {
|
|
12
|
+
total: 0,
|
|
13
|
+
correct: 0,
|
|
14
|
+
incorrect: 0
|
|
15
|
+
},
|
|
16
|
+
specific: {
|
|
17
|
+
tab: {
|
|
18
|
+
total: 0,
|
|
19
|
+
correct: 0,
|
|
20
|
+
incorrect: 0
|
|
21
|
+
},
|
|
22
|
+
left: {
|
|
23
|
+
total: 0,
|
|
24
|
+
correct: 0,
|
|
25
|
+
incorrect: 0
|
|
26
|
+
},
|
|
27
|
+
right: {
|
|
28
|
+
total: 0,
|
|
29
|
+
correct: 0,
|
|
30
|
+
incorrect: 0
|
|
31
|
+
},
|
|
32
|
+
up: {
|
|
33
|
+
total: 0,
|
|
34
|
+
correct: 0,
|
|
35
|
+
incorrect: 0
|
|
36
|
+
},
|
|
37
|
+
down: {
|
|
38
|
+
total: 0,
|
|
39
|
+
correct: 0,
|
|
40
|
+
incorrect: 0
|
|
41
|
+
},
|
|
42
|
+
home: {
|
|
43
|
+
total: 0,
|
|
44
|
+
correct: 0,
|
|
45
|
+
incorrect: 0
|
|
46
|
+
},
|
|
47
|
+
end: {
|
|
48
|
+
total: 0,
|
|
49
|
+
correct: 0,
|
|
50
|
+
incorrect: 0
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
tabElements: {
|
|
55
|
+
total: 0,
|
|
56
|
+
correct: 0,
|
|
57
|
+
incorrect: 0
|
|
58
|
+
},
|
|
59
|
+
tabLists: {
|
|
60
|
+
total: 0,
|
|
61
|
+
correct: 0,
|
|
62
|
+
incorrect: 0
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (withItems) {
|
|
67
|
+
data.tabElements = {
|
|
68
|
+
incorrect: [],
|
|
69
|
+
correct: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Identify an array of the tablists.
|
|
73
|
+
const tabLists = await page.$$('[role=tablist]');
|
|
74
|
+
if (tabLists.length) {
|
|
75
|
+
// FUNCTION DEFINITIONS START
|
|
76
|
+
// Returns text associated with an element.
|
|
77
|
+
const {allText} = require('../procs/test/allText');
|
|
78
|
+
// Returns the index of the focused tab in an array of tabs.
|
|
79
|
+
const focusedTab = async tabs => await page.evaluate(tabs => {
|
|
80
|
+
const focus = document.activeElement;
|
|
81
|
+
return tabs.indexOf(focus);
|
|
82
|
+
}, tabs)
|
|
83
|
+
.catch(error => {
|
|
84
|
+
console.log(`ERROR: could not find focused tab (${error.message})`);
|
|
85
|
+
return -1;
|
|
86
|
+
});
|
|
87
|
+
// Tests a navigation on a tab element.
|
|
88
|
+
const testKey = async (
|
|
89
|
+
tabs, tabElement, keyName, keyProp, goodIndex, elementIsCorrect, itemData
|
|
90
|
+
) => {
|
|
91
|
+
let pressed = true;
|
|
92
|
+
// Click the tab element, to make the focus on it effective.
|
|
93
|
+
await tabElement.click({timeout: 1500})
|
|
94
|
+
.catch(error => {
|
|
95
|
+
console.log(`ERROR: could not click tab element ${itemData.text} (${error.message})`);
|
|
96
|
+
pressed = false;
|
|
97
|
+
});
|
|
98
|
+
if (pressed) {
|
|
99
|
+
// Refocus the tab element and press the specified key (page.keyboard.press may fail).
|
|
100
|
+
await tabElement.press(keyName)
|
|
101
|
+
.catch(error => {
|
|
102
|
+
console.log(`ERROR: could not press ${keyName} (${error.message})`);
|
|
103
|
+
pressed = false;
|
|
104
|
+
});
|
|
105
|
+
if (pressed) {
|
|
106
|
+
// Increment the counts of navigations and key navigations.
|
|
107
|
+
data.totals.navigations.all.total++;
|
|
108
|
+
data.totals.navigations.specific[keyProp].total++;
|
|
109
|
+
// Identify which tab element is now focused, if any.
|
|
110
|
+
const focusIndex = await focusedTab(tabs);
|
|
111
|
+
// If the focus is correct:
|
|
112
|
+
if (focusIndex === goodIndex) {
|
|
113
|
+
// Increment the counts of correct navigations and correct key navigations.
|
|
114
|
+
data.totals.navigations.all.correct++;
|
|
115
|
+
data.totals.navigations.specific[keyProp].correct++;
|
|
116
|
+
}
|
|
117
|
+
// Otherwise, i.e. if the focus is incorrect:
|
|
118
|
+
else {
|
|
119
|
+
// Increment the counts of incorrect navigations and incorrect key navigations.
|
|
120
|
+
data.totals.navigations.all.incorrect++;
|
|
121
|
+
data.totals.navigations.specific[keyProp].incorrect++;
|
|
122
|
+
// Update the element status to incorrect.
|
|
123
|
+
elementIsCorrect = false;
|
|
124
|
+
// If itemization is required:
|
|
125
|
+
if (withItems) {
|
|
126
|
+
// Update the element report.
|
|
127
|
+
itemData.navigationErrors.push(keyName);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return elementIsCorrect;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// Returns the index to which an arrow key should move the focus.
|
|
138
|
+
const arrowTarget = (startIndex, tabCount, orientation, direction) => {
|
|
139
|
+
if (orientation === 'horizontal') {
|
|
140
|
+
if (['up', 'down'].includes(direction)) {
|
|
141
|
+
return startIndex;
|
|
142
|
+
}
|
|
143
|
+
else if (direction === 'left') {
|
|
144
|
+
return startIndex ? startIndex - 1 : tabCount - 1;
|
|
145
|
+
}
|
|
146
|
+
else if (direction === 'right') {
|
|
147
|
+
return startIndex === tabCount - 1 ? 0 : startIndex + 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (orientation === 'vertical') {
|
|
151
|
+
if (['left', 'right'].includes(direction)) {
|
|
152
|
+
return startIndex;
|
|
153
|
+
}
|
|
154
|
+
else if (direction === 'up') {
|
|
155
|
+
return startIndex ? startIndex - 1 : tabCount - 1;
|
|
156
|
+
}
|
|
157
|
+
else if (direction === 'down') {
|
|
158
|
+
return startIndex === tabCount - 1 ? 0 : startIndex + 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
/*
|
|
163
|
+
Recursively tests tablist tab elements (per
|
|
164
|
+
https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel)
|
|
165
|
+
*/
|
|
166
|
+
const testTabs = async (tabs, index, listOrientation, listIsCorrect) => {
|
|
167
|
+
const tabCount = tabs.length;
|
|
168
|
+
// If any tab elements remain to be tested:
|
|
169
|
+
if (index < tabCount) {
|
|
170
|
+
// Increment the reported count of tab elements.
|
|
171
|
+
data.totals.tabElements.total++;
|
|
172
|
+
// Identify the tab element to be tested.
|
|
173
|
+
const currentTab = tabs[index];
|
|
174
|
+
// Initialize it as correct.
|
|
175
|
+
let isCorrect = true;
|
|
176
|
+
const itemData = {};
|
|
177
|
+
// If itemization is required:
|
|
178
|
+
if (withItems) {
|
|
179
|
+
let found = true;
|
|
180
|
+
// Initialize a report on the element.
|
|
181
|
+
itemData.tagName = await page.evaluate(element => element.tagName, currentTab)
|
|
182
|
+
.catch(error => {
|
|
183
|
+
console.log(`ERROR: could not get tag name (${error.message})`);
|
|
184
|
+
found = false;
|
|
185
|
+
return 'ERROR: not found';
|
|
186
|
+
});
|
|
187
|
+
if (found) {
|
|
188
|
+
itemData.text = await allText(page, currentTab);
|
|
189
|
+
itemData.navigationErrors = [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Test the element with each navigation key.
|
|
193
|
+
isCorrect = await testKey(tabs, currentTab, 'Tab', 'tab', -1, isCorrect, itemData);
|
|
194
|
+
isCorrect = await testKey(
|
|
195
|
+
tabs,
|
|
196
|
+
currentTab,
|
|
197
|
+
'ArrowLeft',
|
|
198
|
+
'left',
|
|
199
|
+
arrowTarget(index, tabCount, listOrientation, 'left'),
|
|
200
|
+
isCorrect,
|
|
201
|
+
itemData
|
|
202
|
+
);
|
|
203
|
+
isCorrect = await testKey(
|
|
204
|
+
tabs,
|
|
205
|
+
currentTab,
|
|
206
|
+
'ArrowRight',
|
|
207
|
+
'right',
|
|
208
|
+
arrowTarget(index, tabCount, listOrientation, 'right'),
|
|
209
|
+
isCorrect,
|
|
210
|
+
itemData
|
|
211
|
+
);
|
|
212
|
+
isCorrect = await testKey(
|
|
213
|
+
tabs,
|
|
214
|
+
currentTab,
|
|
215
|
+
'ArrowUp',
|
|
216
|
+
'up',
|
|
217
|
+
arrowTarget(index, tabCount, listOrientation, 'up'),
|
|
218
|
+
isCorrect,
|
|
219
|
+
itemData
|
|
220
|
+
);
|
|
221
|
+
isCorrect = await testKey(
|
|
222
|
+
tabs,
|
|
223
|
+
currentTab,
|
|
224
|
+
'ArrowDown',
|
|
225
|
+
'down',
|
|
226
|
+
arrowTarget(index, tabCount, listOrientation, 'down'),
|
|
227
|
+
isCorrect,
|
|
228
|
+
itemData
|
|
229
|
+
);
|
|
230
|
+
isCorrect = await testKey(tabs, currentTab, 'Home', 'home', 0, isCorrect, itemData);
|
|
231
|
+
isCorrect = await testKey(
|
|
232
|
+
tabs, currentTab, 'End', 'end', tabCount - 1, isCorrect, itemData
|
|
233
|
+
);
|
|
234
|
+
// Update the tablist status (&&= operator from ES 2021 rejected by node 14).
|
|
235
|
+
listIsCorrect = listIsCorrect && isCorrect;
|
|
236
|
+
// Increment the data.
|
|
237
|
+
data.totals.tabElements[isCorrect ? 'correct' : 'incorrect']++;
|
|
238
|
+
if (withItems) {
|
|
239
|
+
data.tabElements[isCorrect ? 'correct' : 'incorrect'].push(itemData);
|
|
240
|
+
}
|
|
241
|
+
// Process the next tab element.
|
|
242
|
+
return await testTabs(tabs, index + 1, listOrientation, listIsCorrect);
|
|
243
|
+
}
|
|
244
|
+
// Otherwise, i.e. if all tab elements have been tested:
|
|
245
|
+
else {
|
|
246
|
+
// Return whether the tablist is correct.
|
|
247
|
+
return listIsCorrect;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
// Recursively tests tablists.
|
|
251
|
+
const testTabLists = async tabLists => {
|
|
252
|
+
// If any tablists remain to be tested:
|
|
253
|
+
if (tabLists.length) {
|
|
254
|
+
const firstTabList = tabLists[0];
|
|
255
|
+
let orientation = await firstTabList.getAttribute('aria-orientation')
|
|
256
|
+
.catch(error=> {
|
|
257
|
+
console.log(`ERROR: could not get tab-list orientation (${error.message})`);
|
|
258
|
+
return 'ERROR';
|
|
259
|
+
});
|
|
260
|
+
if (! orientation) {
|
|
261
|
+
orientation = 'horizontal';
|
|
262
|
+
}
|
|
263
|
+
if (orientation !== 'ERROR') {
|
|
264
|
+
const tabs = await firstTabList.$$('[role=tab]');
|
|
265
|
+
// If the tablist contains at least 2 tab elements:
|
|
266
|
+
if (tabs.length > 1) {
|
|
267
|
+
// Test them.
|
|
268
|
+
const isCorrect = await testTabs(tabs, 0, orientation, true);
|
|
269
|
+
// Increment the data.
|
|
270
|
+
data.totals.tabLists.total++;
|
|
271
|
+
data.totals.tabLists[isCorrect ? 'correct' : 'incorrect']++;
|
|
272
|
+
// Process the remaining tablists.
|
|
273
|
+
await testTabLists(tabLists.slice(1));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
// FUNCTION DEFINITIONS END
|
|
279
|
+
await testTabLists(tabLists);
|
|
280
|
+
}
|
|
281
|
+
return {result: data};
|
|
282
|
+
};
|
package/tests/wave.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
wave
|
|
3
|
+
This test implements the WebAIM WAVE ruleset for accessibility. The 'reportType' argument
|
|
4
|
+
specifies a WAVE report type: 1, 2, 3, or 4. The larger the number, the more detailed (and
|
|
5
|
+
expensive) the report.
|
|
6
|
+
*/
|
|
7
|
+
const https = require('https');
|
|
8
|
+
exports.reporter = async (page, reportType) => {
|
|
9
|
+
const waveKey = process.env.WAVE_KEY;
|
|
10
|
+
// Get the data from a WAVE test.
|
|
11
|
+
const data = await new Promise(resolve => {
|
|
12
|
+
https.get(
|
|
13
|
+
{
|
|
14
|
+
host: 'wave.webaim.org',
|
|
15
|
+
path: `/api/request?key=${waveKey}&url=${page.url()}&reporttype=${reportType}`,
|
|
16
|
+
protocol: 'https:'
|
|
17
|
+
},
|
|
18
|
+
response => {
|
|
19
|
+
let report = '';
|
|
20
|
+
response.on('data', chunk => {
|
|
21
|
+
report += chunk;
|
|
22
|
+
});
|
|
23
|
+
// When the data arrive, return them as an object.
|
|
24
|
+
response.on('end', () => {
|
|
25
|
+
try {
|
|
26
|
+
const result = JSON.parse(report);
|
|
27
|
+
const {categories} = result;
|
|
28
|
+
delete categories.feature;
|
|
29
|
+
delete categories.structure;
|
|
30
|
+
delete categories.aria;
|
|
31
|
+
return resolve(result);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return resolve({
|
|
35
|
+
error: 'WAVE did not return JSON.',
|
|
36
|
+
report
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
return {result: data};
|
|
44
|
+
};
|
package/tests/zIndex.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
zIndex
|
|
3
|
+
This test reports elements with non-auto z indexes. It assumes that pages are most accessible
|
|
4
|
+
when they do not require users to perceive a third dimension (depth). Layers, popups, and dialogs
|
|
5
|
+
that cover other content make it difficult for some or all users to interpret the content and
|
|
6
|
+
know what parts of the content can be acted on. Layering also complicates accessibility control.
|
|
7
|
+
Tests for visibility of focus, for example, may fail if incapable of detecting that a focused
|
|
8
|
+
element is covered by another element.
|
|
9
|
+
*/
|
|
10
|
+
exports.reporter = async (page, withItems) => {
|
|
11
|
+
// Get data on the elements with non-auto z indexes.
|
|
12
|
+
const data = await page.$$eval('body *', (elements, withItems) => {
|
|
13
|
+
// Initialize the data.
|
|
14
|
+
const data = {
|
|
15
|
+
totals: {
|
|
16
|
+
total: 0,
|
|
17
|
+
tagNames: {}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
if (withItems) {
|
|
21
|
+
data.items = [];
|
|
22
|
+
}
|
|
23
|
+
const addElementFacts = element => {
|
|
24
|
+
const tagName = element.tagName;
|
|
25
|
+
const tagNames = data.totals.tagNames;
|
|
26
|
+
if (tagNames[tagName]) {
|
|
27
|
+
tagNames[tagName]++;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
tagNames[tagName] = 1;
|
|
31
|
+
}
|
|
32
|
+
if (withItems) {
|
|
33
|
+
data.items.push({
|
|
34
|
+
tagName,
|
|
35
|
+
id: element.id || '',
|
|
36
|
+
text: element.textContent.trim().replace(/\s{2,}/g, ' ').slice(0, 100)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
elements.forEach(element => {
|
|
41
|
+
if (window.getComputedStyle(element)['z-index'] !== 'auto') {
|
|
42
|
+
data.totals.total++;
|
|
43
|
+
addElementFacts(element);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return data;
|
|
47
|
+
}, withItems);
|
|
48
|
+
return {result: data};
|
|
49
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// sample.js
|
|
2
|
+
// Sample executor for Testaro.
|
|
3
|
+
|
|
4
|
+
const options = {
|
|
5
|
+
reports: `${__dirname}/../reports`,
|
|
6
|
+
// To use the sample batch, uncomment the following line.
|
|
7
|
+
batches: `${__dirname}/../batches/sample.json`,
|
|
8
|
+
script: `${__dirname}/../scripts/app/sample.json`
|
|
9
|
+
};
|
|
10
|
+
const {handleRequest} = require('../../index');
|
|
11
|
+
handleRequest(options);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"what": "Sample Testaro executor with 1 test",
|
|
3
|
+
"strict": true,
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"type": "launch",
|
|
7
|
+
"which": "chromium",
|
|
8
|
+
"what": "Chromium browser"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "url",
|
|
12
|
+
"which": "https://example.com/",
|
|
13
|
+
"what": "simple page, replaced if there is a batch"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "test",
|
|
17
|
+
"which": "bulk",
|
|
18
|
+
"what": "bulk"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"what": "validation of bulk test",
|
|
3
|
+
"strict": true,
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"type": "launch",
|
|
7
|
+
"which": "chromium",
|
|
8
|
+
"what": "usual browser"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "url",
|
|
12
|
+
"which": "file://__dirname/validation/targets/bulk/good.html",
|
|
13
|
+
"what": "small page"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "test",
|
|
17
|
+
"which": "bulk",
|
|
18
|
+
"what": "visible element count",
|
|
19
|
+
"expect": [
|
|
20
|
+
["visibleElements", ">", 1],
|
|
21
|
+
["visibleElements", "<", 5]
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "url",
|
|
26
|
+
"which": "file://__dirname/validation/targets/bulk/bad.html",
|
|
27
|
+
"what": "large page"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "test",
|
|
31
|
+
"which": "bulk",
|
|
32
|
+
"what": "visible element count",
|
|
33
|
+
"expect": [
|
|
34
|
+
["visibleElements", ">", 30],
|
|
35
|
+
["visibleElements", "<", 40]
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"what": "validation of embAc test",
|
|
3
|
+
"strict": true,
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"type": "launch",
|
|
7
|
+
"which": "chromium",
|
|
8
|
+
"what": "usual browser"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "url",
|
|
12
|
+
"which": "file://__dirname/validation/targets/embAc/good.html",
|
|
13
|
+
"what": "page without embedding in links or buttons"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "test",
|
|
17
|
+
"which": "embAc",
|
|
18
|
+
"what": "visible element count",
|
|
19
|
+
"withItems": false,
|
|
20
|
+
"expect": [
|
|
21
|
+
["totals.links", "=", 0],
|
|
22
|
+
["totals.buttons", "=", 0],
|
|
23
|
+
["totals.inputs", "=", 0],
|
|
24
|
+
["totals.selects", "=", 0]
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "url",
|
|
29
|
+
"which": "file://__dirname/validation/targets/embAc/bad.html",
|
|
30
|
+
"what": "page with embeddings in links and buttons"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "test",
|
|
34
|
+
"which": "embAc",
|
|
35
|
+
"what": "visible element count",
|
|
36
|
+
"withItems": false,
|
|
37
|
+
"expect": [
|
|
38
|
+
["totals.links", "=", 2],
|
|
39
|
+
["totals.buttons", "=", 2],
|
|
40
|
+
["totals.inputs", "=", 3],
|
|
41
|
+
["totals.selects", "=", 1]
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|