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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "16.2.0",
3
+ "version": "16.3.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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": "hover",
3
- "what": "validation of hover test",
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": "webkit",
10
- "what": "only compatible browser"
9
+ "which": "chromium",
10
+ "what": "browser"
11
11
  },
12
12
  {
13
13
  "type": "url",
14
- "which": "__targets__/hover/good.html",
15
- "what": "page with standard hover behavior"
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.2",
28
+ "standardResult.totals.1",
29
29
  "=",
30
- 0
30
+ 3
31
31
  ],
32
32
  [
33
- "standardResult.instances.length",
33
+ "standardResult.totals.2",
34
34
  "=",
35
- 0
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
- "hover"
45
+ "hovInd"
65
46
  ],
66
47
  [
67
48
  "standardResult.instances.0.what",
68
49
  "i",
69
- "over the element"
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.2.ordinalSeverity",
53
+ "standardResult.instances.0.ordinalSeverity",
103
54
  "=",
104
- 3
55
+ 2
105
56
  ],
106
57
  [
107
- "standardResult.instances.2.tagName",
58
+ "standardResult.instances.0.tagName",
108
59
  "=",
109
- "BUTTON"
60
+ "A"
110
61
  ],
111
62
  [
112
- "standardResult.instances.2.id",
63
+ "standardResult.instances.0.id",
113
64
  "=",
114
- "smallButton"
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
- "#smallButton"
80
+ "#trigger1"
130
81
  ],
131
82
  [
132
- "standardResult.instances.2.excerpt",
83
+ "standardResult.instances.0.excerpt",
133
84
  "i",
134
- "Trigger 3"
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.length",
157
- ">",
158
- 0
88
+ "standardResult.instances.1.what",
89
+ "i",
90
+ "has a nonstandard"
159
91
  ],
160
92
  [
161
- "standardResult.instances.0.ruleID",
93
+ "standardResult.instances.2.ruleID",
162
94
  "=",
163
- "hover"
95
+ "hovInd"
164
96
  ],
165
97
  [
166
- "standardResult.instances.0.what",
98
+ "standardResult.instances.2.what",
167
99
  "i",
168
- "over elements"
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.totals.0",
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
- 0
105
+ "hovInd"
237
106
  ],
238
107
  [
239
- "standardResult.totals.2",
240
- "=",
241
- 2
108
+ "standardResult.instances.3.what",
109
+ "i",
110
+ "are alike"
242
111
  ],
243
112
  [
244
- "standardResult.totals.3",
113
+ "standardResult.instances.3.ordinalSeverity",
245
114
  "=",
246
115
  1
247
116
  ],
248
117
  [
249
- "standardResult.instances.0.ruleID",
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
- "LI"
120
+ "INPUT"
262
121
  ],
263
122
  [
264
- "standardResult.instances.0.ordinalSeverity",
123
+ "standardResult.instances.3.location.type",
265
124
  "=",
266
- 3
125
+ "box"
267
126
  ],
268
127
  [
269
- "standardResult.instances.0.excerpt",
128
+ "standardResult.instances.3.excerpt",
270
129
  "i",
271
- "loses its cursor"
130
+ "Trigger 4: Enter"
272
131
  ],
273
132
  [
274
- "standardResult.instances.1.ruleID",
133
+ "standardResult.instances.4.ruleID",
275
134
  "=",
276
- "hover"
135
+ "hovInd"
277
136
  ],
278
137
  [
279
- "standardResult.instances.1.what",
138
+ "standardResult.instances.4.what",
280
139
  "i",
281
- "cursor nonstandard"
140
+ "do not change"
282
141
  ],
283
142
  [
284
- "standardResult.instances.1.tagName",
143
+ "standardResult.instances.4.ordinalSeverity",
285
144
  "=",
286
- "A"
145
+ 1
287
146
  ],
288
147
  [
289
- "standardResult.instances.1.id",
148
+ "standardResult.instances.4.tagName",
290
149
  "=",
291
- "trigger1"
150
+ "BUTTON"
292
151
  ],
293
152
  [
294
- "standardResult.instances.1.ordinalSeverity",
295
- "=",
296
- 2
153
+ "standardResult.instances.4.location.spec.x",
154
+ ">",
155
+ 0
297
156
  ],
298
157
  [
299
- "standardResult.instances.1.excerpt",
158
+ "standardResult.instances.4.excerpt",
300
159
  "i",
301
- "Trigger 1"
160
+ "Trigger 5"
302
161
  ]
303
162
  ],
304
163
  "rules": [
305
164
  "y",
306
- "hover"
165
+ "hovInd"
307
166
  ],
308
167
  "args": {
309
- "hover": [4]
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>
@@ -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>