testaro 16.2.0 → 16.3.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/package.json +1 -1
- package/procs/sample.js +30 -0
- package/testaro/hovInd.js +297 -0
- package/testaro/hover.js +2 -23
- package/tests/testaro.js +1 -0
- package/validation/tests/jobs/hovInd.json +57 -198
- package/validation/tests/targets/hovInd/index.html +43 -0
- package/testaro-old/allCaps.js +0 -94
- package/testaro-old/allSlanted.js +0 -86
- package/validation/tests/targets/hovInd/styleBad.html +0 -35
package/package.json
CHANGED
package/procs/sample.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
sample
|
|
3
|
+
Draws a decreasingly index-weighted random sample.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// FUNCTIONS
|
|
7
|
+
|
|
8
|
+
// Draws a location-weighted sample.
|
|
9
|
+
exports.getSample = (population, sampleSize) => {
|
|
10
|
+
const popSize = population.length;
|
|
11
|
+
// If the sample is smaller than the population:
|
|
12
|
+
if (sampleSize < popSize) {
|
|
13
|
+
// Assign to each trigger a priority randomly decreasing with its index.
|
|
14
|
+
const WeightedPopulation = population.map((item, index) => {
|
|
15
|
+
const weight = 1 + Math.sin(Math.PI * index / popSize + Math.PI / 2);
|
|
16
|
+
const priority = weight * Math.random();
|
|
17
|
+
return [index, priority];
|
|
18
|
+
});
|
|
19
|
+
// Return the population indexes of the items in the sample, in ascending order.
|
|
20
|
+
const sortedPopulation = WeightedPopulation.sort((a, b) => b[1] - a[1]);
|
|
21
|
+
const sample = sortedPopulation.slice(0, sampleSize);
|
|
22
|
+
const domOrderSample = sample.sort((a, b) => a[0] - b[0]);
|
|
23
|
+
return domOrderSample.map(trigger => trigger[0]);
|
|
24
|
+
}
|
|
25
|
+
// Otherwise, i.e. if the sample is at least as large as the population:
|
|
26
|
+
else {
|
|
27
|
+
// Return the population indexes.
|
|
28
|
+
return population.map((item, index) => index);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/*
|
|
2
|
+
hover
|
|
3
|
+
This test reports unexpected impacts of hovering on the visible page. Impacts are measured by
|
|
4
|
+
pixel changes outside the hovered element and by unhoverability.
|
|
5
|
+
|
|
6
|
+
The elements that are subjected to hovering (called “triggers”) are the Playwright-visible
|
|
7
|
+
elements that have 'A', 'BUTTON', or (if not with role=menuitem) 'LI' tag names or have
|
|
8
|
+
'onmouseenter' or 'onmouseover' attributes.
|
|
9
|
+
|
|
10
|
+
Despite the delay, the test can make the execution time practical by randomly sampling triggers
|
|
11
|
+
instead of hovering over all of them. When sampling is performed, the results may vary from one
|
|
12
|
+
execution to another. Because hover impacts typically occur near the beginning of a page with
|
|
13
|
+
navigation menus, the probability of the inclusion of a trigger in a sample decreases with the
|
|
14
|
+
index of the trigger.
|
|
15
|
+
|
|
16
|
+
Pixel changes: If no pixel changes occur immediately after an element is hovered over, the page
|
|
17
|
+
is examined once more, after 0.5 second. The greater the fraction of changed pixels, the greater
|
|
18
|
+
the ordinal severity.
|
|
19
|
+
|
|
20
|
+
Unhoverability: An element is reported as unhoverable when it fails the Playwright actionability
|
|
21
|
+
checks for hovering, i.e. fails to be attached to the DOM, visible, stable (not or no longer
|
|
22
|
+
animating), and able to receive events. All triggers satisfy the first two conditions, so only the
|
|
23
|
+
last two might fail. Playwright defines the ability to receive events as being the target of an
|
|
24
|
+
action on the location where the center of the element is, rather than some other element with a
|
|
25
|
+
higher zIndex value in the same location being the target.
|
|
26
|
+
|
|
27
|
+
WARNING: This test uses the Playwright page.screenshot method, which is not implemented for the
|
|
28
|
+
firefox browser type.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// IMPORTS
|
|
32
|
+
|
|
33
|
+
// Module to get locator data.
|
|
34
|
+
const {getLocatorData} = require('../procs/getLocatorData');
|
|
35
|
+
// Module to draw a sample.
|
|
36
|
+
const {getSample} = require('../procs/sample');
|
|
37
|
+
|
|
38
|
+
// CONSTANTS
|
|
39
|
+
|
|
40
|
+
// Standard non-default hover cursors
|
|
41
|
+
const standardCursor = {
|
|
42
|
+
A: 'pointer',
|
|
43
|
+
INPUT: {
|
|
44
|
+
email: 'text',
|
|
45
|
+
image: 'pointer',
|
|
46
|
+
number: 'text',
|
|
47
|
+
password: 'text',
|
|
48
|
+
search: 'text',
|
|
49
|
+
tel: 'text',
|
|
50
|
+
text: 'text',
|
|
51
|
+
url: 'text'
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// FUNCTIONS
|
|
56
|
+
|
|
57
|
+
// Returns the hover-related style properties of a trigger.
|
|
58
|
+
const getHoverStyles = async loc => await loc.evaluate(element => {
|
|
59
|
+
const {
|
|
60
|
+
cursor,
|
|
61
|
+
borderColor,
|
|
62
|
+
borderStyle,
|
|
63
|
+
borderWidth,
|
|
64
|
+
outlineColor,
|
|
65
|
+
outlineStyle,
|
|
66
|
+
outlineWidth,
|
|
67
|
+
outlineOffset,
|
|
68
|
+
backgroundColor
|
|
69
|
+
} = window.getComputedStyle(element);
|
|
70
|
+
return {
|
|
71
|
+
tagName: element.tagName,
|
|
72
|
+
inputType: element.tagName === 'INPUT' ? element.getAttribute('type') || 'text' : null,
|
|
73
|
+
cursor: cursor.replace(/^.+, */, ''),
|
|
74
|
+
border: `${borderColor} ${borderStyle} ${borderWidth}`,
|
|
75
|
+
outline: `${outlineColor} ${outlineStyle} ${outlineWidth} ${outlineOffset}`,
|
|
76
|
+
backgroundColor
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// Returns data on the hover cursor.
|
|
80
|
+
const getCursorData = hovStyles => {
|
|
81
|
+
const {cursor, tagName} = hovStyles;
|
|
82
|
+
const data = {
|
|
83
|
+
cursor
|
|
84
|
+
};
|
|
85
|
+
// If the element is an input or a link:
|
|
86
|
+
if (standardCursor[tagName]) {
|
|
87
|
+
// If it is an input:
|
|
88
|
+
if (tagName === 'INPUT') {
|
|
89
|
+
// Get whether its hover cursor is standard.
|
|
90
|
+
data.ok = cursor === (standardCursor.INPUT[hovStyles.inputType] || 'default');
|
|
91
|
+
}
|
|
92
|
+
// Otherwise, i.e. if it is a link:
|
|
93
|
+
else {
|
|
94
|
+
// Get whether its hover cursor is standard.
|
|
95
|
+
data.ok = cursor === 'pointer';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Otherwise, if it is a button:
|
|
99
|
+
else if (tagName === 'BUTTON') {
|
|
100
|
+
// Get whether its hover cursor is standard.
|
|
101
|
+
data.ok = cursor === 'default';
|
|
102
|
+
}
|
|
103
|
+
// Otherwise, i.e. if it has another type and a hover listener:
|
|
104
|
+
else {
|
|
105
|
+
// Assume its hover cursor is standard.
|
|
106
|
+
data.ok = true;
|
|
107
|
+
}
|
|
108
|
+
return data;
|
|
109
|
+
};
|
|
110
|
+
// Returns whether two hover styles are effectively identical.
|
|
111
|
+
const areAlike = (styles0, styles1) => {
|
|
112
|
+
// Return whether they are effectively identical.
|
|
113
|
+
const areAlike = ['outline', 'border', 'backgroundColor']
|
|
114
|
+
.every(style => styles1[style] === styles0[style]);
|
|
115
|
+
return areAlike;
|
|
116
|
+
};
|
|
117
|
+
// Performs the hovInd test and reports results.
|
|
118
|
+
exports.reporter = async (page, withItems, sampleSize = 20) => {
|
|
119
|
+
// Initialize the result.
|
|
120
|
+
const data = {
|
|
121
|
+
typeTotals: {
|
|
122
|
+
badCursor: 0,
|
|
123
|
+
hoverLikeDefault: 0,
|
|
124
|
+
hoverLikeFocus: 0
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const totals = [0, 0, 0, 0];
|
|
128
|
+
const standardInstances = [];
|
|
129
|
+
// Identify the triggers.
|
|
130
|
+
const selectors = ['a', 'button', 'input', '[onmouseenter]', '[onmouseover]'];
|
|
131
|
+
const selectorString = selectors.map(selector => `body ${selector}:visible`).join(', ');
|
|
132
|
+
const locAll = page.locator(selectorString);
|
|
133
|
+
const locsAll = await locAll.all();
|
|
134
|
+
// Get the population-to-sample ratio.
|
|
135
|
+
const psRatio = Math.max(1, locsAll.length / sampleSize);
|
|
136
|
+
// Get a sample of the triggers.
|
|
137
|
+
const sampleIndexes = getSample(locsAll, sampleSize);
|
|
138
|
+
const sample = locsAll.filter((loc, index) => sampleIndexes.includes(index));
|
|
139
|
+
// For each trigger in the sample:
|
|
140
|
+
for (const loc of sample) {
|
|
141
|
+
// Get its style properties.
|
|
142
|
+
const preStyles = await getHoverStyles(loc);
|
|
143
|
+
// Focus it.
|
|
144
|
+
await loc.focus();
|
|
145
|
+
// Get its style properties.
|
|
146
|
+
const focStyles = await getHoverStyles(loc);
|
|
147
|
+
// Hover over it.
|
|
148
|
+
await loc.hover();
|
|
149
|
+
// Get its style properties.
|
|
150
|
+
const fhStyles = await getHoverStyles(loc);
|
|
151
|
+
// Blur it.
|
|
152
|
+
await loc.blur({
|
|
153
|
+
timeout: 500
|
|
154
|
+
});
|
|
155
|
+
// Get its style properties.
|
|
156
|
+
const hovStyles = await getHoverStyles(loc);
|
|
157
|
+
// If all 4 style declarations belong to the same element:
|
|
158
|
+
if ([focStyles, fhStyles, hovStyles].every(style => style.code === preStyles.code)) {
|
|
159
|
+
// Get data on the element if itemization is required.
|
|
160
|
+
const elData = withItems ? await getLocatorData(loc) : null;
|
|
161
|
+
// If the hover cursor is nonstandard:
|
|
162
|
+
const cursorData = getCursorData(hovStyles);
|
|
163
|
+
if (! cursorData.ok) {
|
|
164
|
+
// Add to the totals.
|
|
165
|
+
totals[2] += psRatio;
|
|
166
|
+
data.typeTotals.badCursor += psRatio;
|
|
167
|
+
// If itemization is required:
|
|
168
|
+
if (withItems) {
|
|
169
|
+
// Add an instance to the result.
|
|
170
|
+
standardInstances.push({
|
|
171
|
+
ruleID: 'hovInd',
|
|
172
|
+
what: `Element has a nonstandard hover cursor (${cursorData.cursor})`,
|
|
173
|
+
ordinalSeverity: 2,
|
|
174
|
+
tagName: elData.tagName,
|
|
175
|
+
id: elData.id,
|
|
176
|
+
location: elData.location,
|
|
177
|
+
excerpt: elData.excerpt
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// If the element is a button and the hover and default states are not distinct:
|
|
182
|
+
if (hovStyles.tagName === 'BUTTON' && areAlike(preStyles, hovStyles)) {
|
|
183
|
+
// Add to the totals.
|
|
184
|
+
totals[1] += psRatio;
|
|
185
|
+
data.typeTotals.hoverLikeDefault += psRatio;
|
|
186
|
+
// If itemization is required:
|
|
187
|
+
if (withItems) {
|
|
188
|
+
// Add an instance to the result.
|
|
189
|
+
standardInstances.push({
|
|
190
|
+
ruleID: 'hovInd',
|
|
191
|
+
what: 'Element border, outline, and background color do not change when hovered over',
|
|
192
|
+
ordinalSeverity: 1,
|
|
193
|
+
tagName: elData.tagName,
|
|
194
|
+
id: elData.id,
|
|
195
|
+
location: elData.location,
|
|
196
|
+
excerpt: elData.excerpt
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// If the hover and focus-hover states are indistinct but differ from the default state:
|
|
201
|
+
if (areAlike(hovStyles, focStyles) && ! areAlike(hovStyles, preStyles)) {
|
|
202
|
+
// Add to the totals.
|
|
203
|
+
totals[1] += psRatio;
|
|
204
|
+
data.typeTotals.hoverLikeFocus += psRatio;
|
|
205
|
+
// If itemization is required:
|
|
206
|
+
if (withItems) {
|
|
207
|
+
// Add an instance to the result.
|
|
208
|
+
standardInstances.push({
|
|
209
|
+
ruleID: 'hovInd',
|
|
210
|
+
what: 'Element border, outline, and background color are alike on hover and focus',
|
|
211
|
+
ordinalSeverity: 1,
|
|
212
|
+
tagName: elData.tagName,
|
|
213
|
+
id: elData.id,
|
|
214
|
+
location: elData.location,
|
|
215
|
+
excerpt: elData.excerpt
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Otherwise, i.e. if the style properties do not all belong to the same element:
|
|
221
|
+
else {
|
|
222
|
+
// Report this.
|
|
223
|
+
data.prevented = true;
|
|
224
|
+
data.error = 'ERROR: Page changes on focus or hover prevent test';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Round the totals.
|
|
228
|
+
Object.keys(data.typeTotals).forEach(rule => {
|
|
229
|
+
data.typeTotals[rule] = Math.round(data.typeTotals[rule]);
|
|
230
|
+
});
|
|
231
|
+
for (const index in totals) {
|
|
232
|
+
totals[index] = Math.round(totals[index]);
|
|
233
|
+
}
|
|
234
|
+
// If itemization is not required:
|
|
235
|
+
if (! withItems) {
|
|
236
|
+
// If any triggers have nonstandard hover cursors:
|
|
237
|
+
if (data.typeTotals.badCursor) {
|
|
238
|
+
// Add a summary instance to the result.
|
|
239
|
+
standardInstances.push({
|
|
240
|
+
ruleID: 'hovInd',
|
|
241
|
+
what: 'Elements have nonstandard hover cursors',
|
|
242
|
+
ordinalSeverity: 2,
|
|
243
|
+
count: data.typeTotals.badCursor,
|
|
244
|
+
tagName: '',
|
|
245
|
+
id: '',
|
|
246
|
+
location: {
|
|
247
|
+
doc: '',
|
|
248
|
+
type: '',
|
|
249
|
+
spec: ''
|
|
250
|
+
},
|
|
251
|
+
excerpt: ''
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
// If any triggers have hover styles not distinct from their default styles:
|
|
255
|
+
if (data.typeTotals.hoverLikeDefault) {
|
|
256
|
+
// Add a summary instance to the result.
|
|
257
|
+
standardInstances.push({
|
|
258
|
+
ruleID: 'hovInd',
|
|
259
|
+
what: 'Element borders, outlines, and background colors do not change when hovered over',
|
|
260
|
+
ordinalSeverity: 1,
|
|
261
|
+
count: data.typeTotals.hoverLikeDefault,
|
|
262
|
+
tagName: '',
|
|
263
|
+
id: '',
|
|
264
|
+
location: {
|
|
265
|
+
doc: '',
|
|
266
|
+
type: '',
|
|
267
|
+
spec: ''
|
|
268
|
+
},
|
|
269
|
+
excerpt: ''
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// If any triggers have focus-hover styles not distinct from their focus styles:
|
|
273
|
+
if (data.typeTotals.fhLikeFocus) {
|
|
274
|
+
// Add a summary instance to the result.
|
|
275
|
+
standardInstances.push({
|
|
276
|
+
ruleID: 'hovInd',
|
|
277
|
+
what: 'Element borders, outlines, and background colors on focus do not change when also hovered over',
|
|
278
|
+
ordinalSeverity: 1,
|
|
279
|
+
count: data.typeTotals.fhLikeFocus,
|
|
280
|
+
tagName: '',
|
|
281
|
+
id: '',
|
|
282
|
+
location: {
|
|
283
|
+
doc: '',
|
|
284
|
+
type: '',
|
|
285
|
+
spec: ''
|
|
286
|
+
},
|
|
287
|
+
excerpt: ''
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Return the result.
|
|
292
|
+
return {
|
|
293
|
+
data,
|
|
294
|
+
totals,
|
|
295
|
+
standardInstances
|
|
296
|
+
};
|
|
297
|
+
};
|
package/testaro/hover.js
CHANGED
|
@@ -32,34 +32,13 @@
|
|
|
32
32
|
|
|
33
33
|
// Module to get locator data.
|
|
34
34
|
const {getLocatorData} = require('../procs/getLocatorData');
|
|
35
|
+
// Module to draw a sample.
|
|
36
|
+
const {getSample} = require('../procs/sample');
|
|
35
37
|
// Module to get pixel changes between two times.
|
|
36
38
|
const {visChange} = require('../procs/visChange');
|
|
37
39
|
|
|
38
40
|
// FUNCTIONS
|
|
39
41
|
|
|
40
|
-
// Draws a location-weighted sample of triggers.
|
|
41
|
-
const getSample = (population, sampleSize) => {
|
|
42
|
-
const popSize = population.length;
|
|
43
|
-
// If the sample is smaller than the population:
|
|
44
|
-
if (sampleSize < popSize) {
|
|
45
|
-
// Assign to each trigger a priority randomly decreasing with its index.
|
|
46
|
-
const WeightedPopulation = population.map((trigger, index) => {
|
|
47
|
-
const weight = 1 + Math.sin(Math.PI * index / popSize + Math.PI / 2);
|
|
48
|
-
const priority = weight * Math.random();
|
|
49
|
-
return [index, priority];
|
|
50
|
-
});
|
|
51
|
-
// Return the indexes of the triggers with the highest priorities.
|
|
52
|
-
const sortedPopulation = WeightedPopulation.sort((a, b) => b[1] - a[1]);
|
|
53
|
-
const sample = sortedPopulation.slice(0, sampleSize);
|
|
54
|
-
const domOrderSample = sample.sort((a, b) => a[0] - b[0]);
|
|
55
|
-
return domOrderSample.map(trigger => trigger[0]);
|
|
56
|
-
}
|
|
57
|
-
// Otherwise, i.e. if the sample is at least as large as the population:
|
|
58
|
-
else {
|
|
59
|
-
// Return the population indexes.
|
|
60
|
-
return population.map((trigger, index) => index);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
42
|
// Performs the hover test and reports results.
|
|
64
43
|
exports.reporter = async (page, withItems, sampleSize = 20) => {
|
|
65
44
|
// Initialize the result.
|
package/tests/testaro.js
CHANGED
|
@@ -22,6 +22,7 @@ const evalRules = {
|
|
|
22
22
|
focOp: 'discrepancies between focusability and operability',
|
|
23
23
|
focVis: 'links that are invisible when focused',
|
|
24
24
|
hover: 'hover-caused content changes',
|
|
25
|
+
hovInd: 'hover indication nonstandard',
|
|
25
26
|
labClash: 'labeling inconsistencies',
|
|
26
27
|
lineHeight: 'text with a line height less than 1.5 times its font size',
|
|
27
28
|
linkExt: 'links that automatically open new windows',
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
3
|
-
"what": "validation of
|
|
2
|
+
"id": "hovInd",
|
|
3
|
+
"what": "validation of hovInd test",
|
|
4
4
|
"strict": true,
|
|
5
5
|
"timeLimit": 40,
|
|
6
6
|
"acts": [
|
|
7
7
|
{
|
|
8
8
|
"type": "launch",
|
|
9
|
-
"which": "
|
|
10
|
-
"what": "
|
|
9
|
+
"which": "chromium",
|
|
10
|
+
"what": "browser"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"type": "url",
|
|
14
|
-
"which": "__targets__/
|
|
15
|
-
"what": "page with
|
|
14
|
+
"which": "__targets__/hovInd/index.html",
|
|
15
|
+
"what": "page with varied hover indicators"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"type": "test",
|
|
@@ -25,93 +25,44 @@
|
|
|
25
25
|
0
|
|
26
26
|
],
|
|
27
27
|
[
|
|
28
|
-
"standardResult.totals.
|
|
28
|
+
"standardResult.totals.1",
|
|
29
29
|
"=",
|
|
30
|
-
|
|
30
|
+
3
|
|
31
31
|
],
|
|
32
32
|
[
|
|
33
|
-
"standardResult.
|
|
33
|
+
"standardResult.totals.2",
|
|
34
34
|
"=",
|
|
35
|
-
|
|
36
|
-
]
|
|
37
|
-
],
|
|
38
|
-
"rules": [
|
|
39
|
-
"y",
|
|
40
|
-
"hover"
|
|
41
|
-
],
|
|
42
|
-
"args": {
|
|
43
|
-
"hover": [5]
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"type": "url",
|
|
48
|
-
"which": "__targets__/hover/bad.html",
|
|
49
|
-
"what": "page with deviant hover behavior"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"type": "test",
|
|
53
|
-
"which": "testaro",
|
|
54
|
-
"withItems": true,
|
|
55
|
-
"expect": [
|
|
35
|
+
2
|
|
36
|
+
],
|
|
56
37
|
[
|
|
57
38
|
"standardResult.totals.3",
|
|
58
|
-
"
|
|
39
|
+
"=",
|
|
59
40
|
0
|
|
60
41
|
],
|
|
61
42
|
[
|
|
62
43
|
"standardResult.instances.0.ruleID",
|
|
63
44
|
"=",
|
|
64
|
-
"
|
|
45
|
+
"hovInd"
|
|
65
46
|
],
|
|
66
47
|
[
|
|
67
48
|
"standardResult.instances.0.what",
|
|
68
49
|
"i",
|
|
69
|
-
"
|
|
70
|
-
],
|
|
71
|
-
[
|
|
72
|
-
"standardResult.instances.0.tagName",
|
|
73
|
-
"=",
|
|
74
|
-
"A"
|
|
75
|
-
],
|
|
76
|
-
[
|
|
77
|
-
"standardResult.instances.0.location.doc",
|
|
78
|
-
"=",
|
|
79
|
-
"dom"
|
|
80
|
-
],
|
|
81
|
-
[
|
|
82
|
-
"standardResult.instances.0.location.type",
|
|
83
|
-
"=",
|
|
84
|
-
"box"
|
|
85
|
-
],
|
|
86
|
-
[
|
|
87
|
-
"standardResult.instances.0.location.spec.height",
|
|
88
|
-
"<",
|
|
89
|
-
"20"
|
|
90
|
-
],
|
|
91
|
-
[
|
|
92
|
-
"standardResult.instances.0.excerpt",
|
|
93
|
-
"i",
|
|
94
|
-
"Trigger 1"
|
|
95
|
-
],
|
|
96
|
-
[
|
|
97
|
-
"standardResult.instances.2.what",
|
|
98
|
-
"i",
|
|
99
|
-
"is not hoverable"
|
|
50
|
+
"has a nonstandard"
|
|
100
51
|
],
|
|
101
52
|
[
|
|
102
|
-
"standardResult.instances.
|
|
53
|
+
"standardResult.instances.0.ordinalSeverity",
|
|
103
54
|
"=",
|
|
104
|
-
|
|
55
|
+
2
|
|
105
56
|
],
|
|
106
57
|
[
|
|
107
|
-
"standardResult.instances.
|
|
58
|
+
"standardResult.instances.0.tagName",
|
|
108
59
|
"=",
|
|
109
|
-
"
|
|
60
|
+
"A"
|
|
110
61
|
],
|
|
111
62
|
[
|
|
112
|
-
"standardResult.instances.
|
|
63
|
+
"standardResult.instances.0.id",
|
|
113
64
|
"=",
|
|
114
|
-
"
|
|
65
|
+
"trigger1"
|
|
115
66
|
],
|
|
116
67
|
[
|
|
117
68
|
"standardResult.instances.0.location.doc",
|
|
@@ -126,187 +77,95 @@
|
|
|
126
77
|
[
|
|
127
78
|
"standardResult.instances.0.location.spec",
|
|
128
79
|
"=",
|
|
129
|
-
"#
|
|
80
|
+
"#trigger1"
|
|
130
81
|
],
|
|
131
82
|
[
|
|
132
|
-
"standardResult.instances.
|
|
83
|
+
"standardResult.instances.0.excerpt",
|
|
133
84
|
"i",
|
|
134
|
-
"Trigger
|
|
135
|
-
]
|
|
136
|
-
],
|
|
137
|
-
"rules": [
|
|
138
|
-
"y",
|
|
139
|
-
"hover"
|
|
140
|
-
],
|
|
141
|
-
"args": {
|
|
142
|
-
"hover": [6]
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"type": "test",
|
|
147
|
-
"which": "testaro",
|
|
148
|
-
"withItems": false,
|
|
149
|
-
"expect": [
|
|
150
|
-
[
|
|
151
|
-
"standardResult.totals.3",
|
|
152
|
-
">",
|
|
153
|
-
0
|
|
85
|
+
"Trigger 1"
|
|
154
86
|
],
|
|
155
87
|
[
|
|
156
|
-
"standardResult.instances.
|
|
157
|
-
"
|
|
158
|
-
|
|
88
|
+
"standardResult.instances.1.what",
|
|
89
|
+
"i",
|
|
90
|
+
"has a nonstandard"
|
|
159
91
|
],
|
|
160
92
|
[
|
|
161
|
-
"standardResult.instances.
|
|
93
|
+
"standardResult.instances.2.ruleID",
|
|
162
94
|
"=",
|
|
163
|
-
"
|
|
95
|
+
"hovInd"
|
|
164
96
|
],
|
|
165
97
|
[
|
|
166
|
-
"standardResult.instances.
|
|
98
|
+
"standardResult.instances.2.what",
|
|
167
99
|
"i",
|
|
168
|
-
"
|
|
169
|
-
],
|
|
170
|
-
[
|
|
171
|
-
"standardResult.instances.0.ordinalSeverity",
|
|
172
|
-
">",
|
|
173
|
-
-1
|
|
174
|
-
],
|
|
175
|
-
[
|
|
176
|
-
"standardResult.instances.0.count",
|
|
177
|
-
">",
|
|
178
|
-
0
|
|
179
|
-
]
|
|
180
|
-
],
|
|
181
|
-
"rules": [
|
|
182
|
-
"y",
|
|
183
|
-
"hover"
|
|
184
|
-
],
|
|
185
|
-
"args": {
|
|
186
|
-
"hover": [6]
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
"type": "test",
|
|
191
|
-
"which": "testaro",
|
|
192
|
-
"withItems": false,
|
|
193
|
-
"expect": [
|
|
194
|
-
[
|
|
195
|
-
"standardResult.totals.3",
|
|
196
|
-
"<",
|
|
197
|
-
7
|
|
198
|
-
],
|
|
199
|
-
[
|
|
200
|
-
"standardResult.totals.2",
|
|
201
|
-
"<",
|
|
202
|
-
4
|
|
203
|
-
],
|
|
204
|
-
[
|
|
205
|
-
"standardResult.totals.1",
|
|
206
|
-
"<",
|
|
207
|
-
13
|
|
100
|
+
"do not change"
|
|
208
101
|
],
|
|
209
102
|
[
|
|
210
|
-
"standardResult.
|
|
211
|
-
"<",
|
|
212
|
-
4
|
|
213
|
-
]
|
|
214
|
-
],
|
|
215
|
-
"rules": [
|
|
216
|
-
"y",
|
|
217
|
-
"hover"
|
|
218
|
-
],
|
|
219
|
-
"args": {
|
|
220
|
-
"hover": [2]
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
"type": "url",
|
|
225
|
-
"which": "__targets__/hover/styleBad.html",
|
|
226
|
-
"what": "page with deviant trigger styles"
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
"type": "test",
|
|
230
|
-
"which": "testaro",
|
|
231
|
-
"withItems": true,
|
|
232
|
-
"expect": [
|
|
233
|
-
[
|
|
234
|
-
"standardResult.totals.1",
|
|
103
|
+
"standardResult.instances.3.ruleID",
|
|
235
104
|
"=",
|
|
236
|
-
|
|
105
|
+
"hovInd"
|
|
237
106
|
],
|
|
238
107
|
[
|
|
239
|
-
"standardResult.
|
|
240
|
-
"
|
|
241
|
-
|
|
108
|
+
"standardResult.instances.3.what",
|
|
109
|
+
"i",
|
|
110
|
+
"are alike"
|
|
242
111
|
],
|
|
243
112
|
[
|
|
244
|
-
"standardResult.
|
|
113
|
+
"standardResult.instances.3.ordinalSeverity",
|
|
245
114
|
"=",
|
|
246
115
|
1
|
|
247
116
|
],
|
|
248
117
|
[
|
|
249
|
-
"standardResult.instances.
|
|
250
|
-
"=",
|
|
251
|
-
"hover"
|
|
252
|
-
],
|
|
253
|
-
[
|
|
254
|
-
"standardResult.instances.0.what",
|
|
255
|
-
"i",
|
|
256
|
-
"hides"
|
|
257
|
-
],
|
|
258
|
-
[
|
|
259
|
-
"standardResult.instances.0.tagName",
|
|
118
|
+
"standardResult.instances.3.tagName",
|
|
260
119
|
"=",
|
|
261
|
-
"
|
|
120
|
+
"INPUT"
|
|
262
121
|
],
|
|
263
122
|
[
|
|
264
|
-
"standardResult.instances.
|
|
123
|
+
"standardResult.instances.3.location.type",
|
|
265
124
|
"=",
|
|
266
|
-
|
|
125
|
+
"box"
|
|
267
126
|
],
|
|
268
127
|
[
|
|
269
|
-
"standardResult.instances.
|
|
128
|
+
"standardResult.instances.3.excerpt",
|
|
270
129
|
"i",
|
|
271
|
-
"
|
|
130
|
+
"Trigger 4: Enter"
|
|
272
131
|
],
|
|
273
132
|
[
|
|
274
|
-
"standardResult.instances.
|
|
133
|
+
"standardResult.instances.4.ruleID",
|
|
275
134
|
"=",
|
|
276
|
-
"
|
|
135
|
+
"hovInd"
|
|
277
136
|
],
|
|
278
137
|
[
|
|
279
|
-
"standardResult.instances.
|
|
138
|
+
"standardResult.instances.4.what",
|
|
280
139
|
"i",
|
|
281
|
-
"
|
|
140
|
+
"do not change"
|
|
282
141
|
],
|
|
283
142
|
[
|
|
284
|
-
"standardResult.instances.
|
|
143
|
+
"standardResult.instances.4.ordinalSeverity",
|
|
285
144
|
"=",
|
|
286
|
-
|
|
145
|
+
1
|
|
287
146
|
],
|
|
288
147
|
[
|
|
289
|
-
"standardResult.instances.
|
|
148
|
+
"standardResult.instances.4.tagName",
|
|
290
149
|
"=",
|
|
291
|
-
"
|
|
150
|
+
"BUTTON"
|
|
292
151
|
],
|
|
293
152
|
[
|
|
294
|
-
"standardResult.instances.
|
|
295
|
-
"
|
|
296
|
-
|
|
153
|
+
"standardResult.instances.4.location.spec.x",
|
|
154
|
+
">",
|
|
155
|
+
0
|
|
297
156
|
],
|
|
298
157
|
[
|
|
299
|
-
"standardResult.instances.
|
|
158
|
+
"standardResult.instances.4.excerpt",
|
|
300
159
|
"i",
|
|
301
|
-
"Trigger
|
|
160
|
+
"Trigger 5"
|
|
302
161
|
]
|
|
303
162
|
],
|
|
304
163
|
"rules": [
|
|
305
164
|
"y",
|
|
306
|
-
"
|
|
165
|
+
"hovInd"
|
|
307
166
|
],
|
|
308
167
|
"args": {
|
|
309
|
-
"
|
|
168
|
+
"hovInd": [7]
|
|
310
169
|
}
|
|
311
170
|
}
|
|
312
171
|
],
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with various hover indicators</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style>
|
|
9
|
+
.ambig:hover, .ambig:focus {
|
|
10
|
+
color: blue;
|
|
11
|
+
background-color: yellow;
|
|
12
|
+
outline: 2px solid yellow
|
|
13
|
+
}
|
|
14
|
+
.cursorless {
|
|
15
|
+
cursor: none;
|
|
16
|
+
}
|
|
17
|
+
.hoverBC:hover {
|
|
18
|
+
background-color: #fff;
|
|
19
|
+
}
|
|
20
|
+
.inert, .inert:hover {
|
|
21
|
+
background-color: #def;
|
|
22
|
+
border: 2px solid blue;
|
|
23
|
+
}
|
|
24
|
+
.qCursor {
|
|
25
|
+
cursor: help;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<main>
|
|
31
|
+
<h1>Page with various hover indicators</h1>
|
|
32
|
+
<p>This <a href="https://www.w3.org/">Trigger 0</a> is plain.</p>
|
|
33
|
+
<p>Here is a link named <a id="trigger1" class="qCursor" href="https://en.wikipedia.org">Trigger 1</a>. When hovered over, it wrongly changes the cursor to a question mark.</p>
|
|
34
|
+
<p>This is a button named <button class="cursorless">Trigger 2</button>. It makes the cursor invisible when being hovered over. It also makes no change to its other styles on hover. The browser may slightly darken the background color on hover, but keeps the computed background-color style unchanged. Since the change by the browser is almost imperceptible, this test treats it as no change.</p>
|
|
35
|
+
<p>This is an input with a default type. <label>Trigger 3: Enter something <input></label></p>
|
|
36
|
+
<p>This is an email input that fails to distinguish hover from focus states. <label>Trigger 4: Enter an email address <input class="ambig" type="email"></label></p>
|
|
37
|
+
<p>This is a button named <button class="inert">Trigger 5</button>. It does not change when hovered over.</p>
|
|
38
|
+
<p>This is a button named <button class="hoverBC">Trigger 6</button>. Its background color changes when hovered over.</p>
|
|
39
|
+
<p>Trigger 1 has a bad hover cursor. Trigger 2 has no hover cursor. Trigger 4 has the same focus and hover indicator. Trigger 5 has no hover indicator.</p>
|
|
40
|
+
<p>Impact severities: bad cursor 2 (2 instances), ambiguous indicator 1 (1 instance), missing hover indicator 1 (2 instances).
|
|
41
|
+
</main>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
package/testaro-old/allCaps.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
allCaps
|
|
3
|
-
Related to Tenon rule 153.
|
|
4
|
-
This test reports leaf elements whose text contents contain at least one substring of upper-case
|
|
5
|
-
letters, hyphen-minuses, and spaces at least 8 characters long and no lower-case letters. Blocks
|
|
6
|
-
of upper-case text are difficult to read.
|
|
7
|
-
*/
|
|
8
|
-
// Runs the test and returns the results.
|
|
9
|
-
exports.reporter = async (page, withItems) => {
|
|
10
|
-
// Identify the elements with upper-case text longer than 7 characters.
|
|
11
|
-
const data = await page.$$eval('body *', (elements, withItems) => {
|
|
12
|
-
// Returns a space-minimized copy of a string.
|
|
13
|
-
const compact = string => string
|
|
14
|
-
.replace(/[\t\n]/g, '')
|
|
15
|
-
.replace(/\s{2,}/g, ' ')
|
|
16
|
-
.trim()
|
|
17
|
-
.slice(0, 100);
|
|
18
|
-
// Get the leaf elements.
|
|
19
|
-
const leafElements = elements.filter(element => ! element.children.length);
|
|
20
|
-
// Get those with text contents longer than 7 characters.
|
|
21
|
-
const textElements = leafElements.filter(element => compact(element.textContent).length > 7);
|
|
22
|
-
// Get those that are reportable.
|
|
23
|
-
const allCapElements = textElements.filter(element => {
|
|
24
|
-
const {textContent} = element;
|
|
25
|
-
const elementText = compact(textContent);
|
|
26
|
-
if (elementText === elementText.toUpperCase() && /[-A-Z ]{8}/.test(elementText)) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
const styleDec = window.getComputedStyle(element);
|
|
31
|
-
return styleDec['text-transform'] === 'uppercase' && /[-A-Za-z ]{8}/.test(elementText);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
// Initialize the result.
|
|
35
|
-
const data = {
|
|
36
|
-
total: allCapElements.length
|
|
37
|
-
};
|
|
38
|
-
// If itemization is required:
|
|
39
|
-
if (withItems) {
|
|
40
|
-
// Add an itemization to the result.
|
|
41
|
-
data.items = [];
|
|
42
|
-
allCapElements.forEach(allCapElement => {
|
|
43
|
-
data.items.push({
|
|
44
|
-
tagName: allCapElement.tagName,
|
|
45
|
-
id: allCapElement.id || '',
|
|
46
|
-
text: compact(allCapElement.textContent)
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return data;
|
|
51
|
-
}, withItems);
|
|
52
|
-
// Get the totals.
|
|
53
|
-
const totals = [data.total, 0, 0, 0];
|
|
54
|
-
// Get the required standard instances.
|
|
55
|
-
const standardInstances = [];
|
|
56
|
-
if (data.items) {
|
|
57
|
-
data.items.forEach(item => {
|
|
58
|
-
standardInstances.push({
|
|
59
|
-
ruleID: 'allCaps',
|
|
60
|
-
what: `${item.tagName} element has entirely upper-case text`,
|
|
61
|
-
ordinalSeverity: 0,
|
|
62
|
-
tagName: item.tagName,
|
|
63
|
-
id: item.id,
|
|
64
|
-
location: {
|
|
65
|
-
doc: '',
|
|
66
|
-
type: '',
|
|
67
|
-
spec: ''
|
|
68
|
-
},
|
|
69
|
-
excerpt: item.text
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
standardInstances.push({
|
|
75
|
-
ruleID: 'allCaps',
|
|
76
|
-
what: 'Elements have entirely upper-case texts',
|
|
77
|
-
ordinalSeverity: 0,
|
|
78
|
-
count: data.total,
|
|
79
|
-
tagName: '',
|
|
80
|
-
id: '',
|
|
81
|
-
location: {
|
|
82
|
-
doc: '',
|
|
83
|
-
type: '',
|
|
84
|
-
spec: ''
|
|
85
|
-
},
|
|
86
|
-
excerpt: ''
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
data,
|
|
91
|
-
totals,
|
|
92
|
-
standardInstances
|
|
93
|
-
};
|
|
94
|
-
};
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
allSlanted
|
|
3
|
-
Related to Tenon rule 154.
|
|
4
|
-
This test reports leaf elements whose text contents are at least 40 characters long and are
|
|
5
|
-
entirely italic or oblique. Blocks of italic or oblique text are difficult to read.
|
|
6
|
-
*/
|
|
7
|
-
// Runs the test and returns the results.
|
|
8
|
-
exports.reporter = async (page, withItems) => {
|
|
9
|
-
// Identify the elements with text longer than 7 characters.
|
|
10
|
-
const data = await page.$$eval('body *', (elements, withItems) => {
|
|
11
|
-
// Returns a space-minimized copy of a string.
|
|
12
|
-
const compact = string => string
|
|
13
|
-
.replace(/[\t\n]/g, '')
|
|
14
|
-
.replace(/\s{2,}/g, ' ')
|
|
15
|
-
.trim()
|
|
16
|
-
.slice(0, 100);
|
|
17
|
-
// Get the leaf elements.
|
|
18
|
-
const leafElements = elements.filter(element => ! element.children.length);
|
|
19
|
-
// Get those with text contents longer than 39 characters.
|
|
20
|
-
const textElements = leafElements.filter(element => compact(element.textContent).length > 39);
|
|
21
|
-
// Get those with italic or oblique text.
|
|
22
|
-
const allSlantedElements = textElements.filter(element => {
|
|
23
|
-
const styleDec = window.getComputedStyle(element);
|
|
24
|
-
return ['italic', 'oblique'].includes(styleDec['font-style']);
|
|
25
|
-
});
|
|
26
|
-
// Initialize the result.
|
|
27
|
-
const data = {
|
|
28
|
-
total: allSlantedElements.length
|
|
29
|
-
};
|
|
30
|
-
// If itemization is required:
|
|
31
|
-
if (withItems) {
|
|
32
|
-
// Add an itemization to the result.
|
|
33
|
-
data.items = [];
|
|
34
|
-
allSlantedElements.forEach(allSlantedElement => {
|
|
35
|
-
data.items.push({
|
|
36
|
-
tagName: allSlantedElement.tagName,
|
|
37
|
-
id: allSlantedElement.id || '',
|
|
38
|
-
text: compact(allSlantedElement.textContent)
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
return data;
|
|
43
|
-
}, withItems);
|
|
44
|
-
// Get the totals.
|
|
45
|
-
const totals = [data.total, 0, 0, 0];
|
|
46
|
-
// Get the required standard instances.
|
|
47
|
-
const standardInstances = [];
|
|
48
|
-
if (data.items) {
|
|
49
|
-
data.items.forEach(item => {
|
|
50
|
-
standardInstances.push({
|
|
51
|
-
ruleID: 'allSlanted',
|
|
52
|
-
what: `${item.tagName} element has entirely italic or oblique text`,
|
|
53
|
-
ordinalSeverity: 0,
|
|
54
|
-
tagName: item.tagName.toUpperCase(),
|
|
55
|
-
id: item.id,
|
|
56
|
-
location: {
|
|
57
|
-
doc: '',
|
|
58
|
-
type: '',
|
|
59
|
-
spec: ''
|
|
60
|
-
},
|
|
61
|
-
excerpt: item.text
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
standardInstances.push({
|
|
67
|
-
ruleID: 'allSlanted',
|
|
68
|
-
what: 'Elements have entirely italic or oblique texts',
|
|
69
|
-
ordinalSeverity: 0,
|
|
70
|
-
count: data.total,
|
|
71
|
-
tagName: '',
|
|
72
|
-
id: '',
|
|
73
|
-
location: {
|
|
74
|
-
doc: '',
|
|
75
|
-
type: '',
|
|
76
|
-
spec: ''
|
|
77
|
-
},
|
|
78
|
-
excerpt: ''
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
data,
|
|
83
|
-
totals,
|
|
84
|
-
standardInstances
|
|
85
|
-
};
|
|
86
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en-US">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>Page with deviant hover indicators</title>
|
|
6
|
-
<meta name="description" content="tester">
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
-
<style>
|
|
9
|
-
a.qCursor {
|
|
10
|
-
cursor: help;
|
|
11
|
-
}
|
|
12
|
-
li.cursorless {
|
|
13
|
-
cursor: none;
|
|
14
|
-
}
|
|
15
|
-
li.hoverChanger:hover {
|
|
16
|
-
color: blue;
|
|
17
|
-
background-color: yellow;
|
|
18
|
-
}
|
|
19
|
-
</style>
|
|
20
|
-
</head>
|
|
21
|
-
<body>
|
|
22
|
-
<main>
|
|
23
|
-
<h1>Page with deviant hover indicators</h1>
|
|
24
|
-
<p>This page contains a link named <a id="trigger1" class="qCursor" href="https://en.wikipedia.org">Trigger 1</a>. When hovered over, it changes the cursor to a question mark.</p>
|
|
25
|
-
<p>This is a list.</p>
|
|
26
|
-
<ul>
|
|
27
|
-
<li>This Trigger 2 is a normal list item.</li>
|
|
28
|
-
<li class="cursorless">This Trigger 3 is a list item that loses its cursor when hovered over.</li>
|
|
29
|
-
<li id="li2" class="hoverChanger">This Trigger 4 is a list item that changes when hovered over, although it should not.</li>
|
|
30
|
-
</ul>
|
|
31
|
-
<p>Trigger 1 has a bad hover cursor. Trigger 3 has no hover cursor. Trigger 4 changes style when hovered over.</p>
|
|
32
|
-
<p>Impacts severities: no cursor 3, bad cursor 2, bad indicator 2.
|
|
33
|
-
</main>
|
|
34
|
-
</body>
|
|
35
|
-
</html>
|