testaro 64.2.0 → 64.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": "64.2.0",
3
+ "version": "64.3.0",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/testaro.js CHANGED
@@ -50,9 +50,11 @@ exports.doTest = async (
50
50
  // Get all candidates.
51
51
  const candidates = document.body.querySelectorAll(candidateSelector);
52
52
  let violationCount = 0;
53
- const instances = [];
53
+ const standardInstances = [];
54
54
  // Get a function that returns a violation description, if any, for the candidate.
55
55
  const getBadWhat = eval(`(${getBadWhatString})`);
56
+ let data = {};
57
+ const totals = [0, 0, 0, 0];
56
58
  // For each candidate:
57
59
  for (const candidate of candidates) {
58
60
  // Get the violation description, if any.
@@ -61,35 +63,55 @@ exports.doTest = async (
61
63
  if (violationWhat) {
62
64
  // Increment the violation count.
63
65
  violationCount++;
66
+ let ruleWhat;
67
+ const violationType = typeof violationWhat;
68
+ // If data on the violation were provided:
69
+ if (violationType === 'object') {
70
+ // Get the description and add the data to the rule data.
71
+ ruleWhat = violationWhat.description;
72
+ data[violationCount - 1] = violationWhat.data;
73
+ }
74
+ // Otherwise, i.e. if only a description of the violation was provided:
75
+ else if (violationType === 'string') {
76
+ // Get it.
77
+ ruleWhat = violationWhat;
78
+ }
64
79
  // If itemization is required:
65
80
  if (withItems) {
66
- const violationWhatStart = violationWhat.slice(0, 2);
67
- let ruleSeverity = severity;
68
- let ruleWhat = violationWhat
81
+ const ruleWhatStart = ruleWhat.slice(0, 2);
82
+ let instanceSeverity = severity;
69
83
  // If this violation has a custom severity:
70
- if (/[0-3]:/.test(violationWhatStart)) {
71
- // Get it and remove it from the violation description.
72
- ruleSeverity = Number(violationWhat[0]);
73
- ruleWhat = violationWhat.slice(2);
84
+ if (/[0-3]:/.test(ruleWhatStart)) {
85
+ // Get it.
86
+ instanceSeverity = Number(ruleWhat[0]);
87
+ // Remove it from the violation description.
88
+ ruleWhat = ruleWhat.slice(2);
89
+ // Increment the violation totals.
90
+ totals[instanceSeverity]++;
74
91
  }
75
92
  // Add an instance to the instances.
76
- instances.push(
77
- window.getInstance(candidate, ruleID, ruleWhat, 1, ruleSeverity)
93
+ standardInstances.push(
94
+ window.getInstance(candidate, ruleID, ruleWhat, 1, instanceSeverity)
78
95
  );
79
96
  }
97
+ // Otherwise, i.e. if itemization is not required:
98
+ else {
99
+ // Increment the violation totals.
100
+ totals[severity]++;
101
+ }
80
102
  }
81
103
  }
82
104
  // If there are any violations and itemization is not required:
83
105
  if (violationCount && ! withItems) {
84
106
  // Add a summary instance to the instances.
85
- instances.push(
107
+ standardInstances.push(
86
108
  window.getInstance(null, ruleID, whats, violationCount, severity, summaryTagName)
87
109
  );
88
110
  }
89
111
  return {
90
- data: {},
91
- totals: [0, 0, 0, violationCount],
92
- standardInstances: instances
112
+ data,
113
+ totals,
114
+ standardInstances
93
115
  }
94
116
  }, [
95
117
  withItems,
package/testaro/hovInd.js CHANGED
@@ -10,290 +10,135 @@
10
10
 
11
11
  /*
12
12
  hovInd
13
- This test reports nonstandard hover indication.
13
+ This test reports confusing hover indication.
14
14
  */
15
15
 
16
16
  // IMPORTS
17
17
 
18
- // Module to get locator data.
19
- const {getLocatorData} = require('../procs/getLocatorData');
20
- // Module to draw a sample.
21
- const {getSample} = require('../procs/sample');
22
-
23
- // CONSTANTS
24
-
25
- // Standard non-default hover cursors
26
- const standardCursor = {
27
- A: 'pointer',
28
- INPUT: {
29
- email: 'text',
30
- image: 'pointer',
31
- number: 'text',
32
- password: 'text',
33
- search: 'text',
34
- tel: 'text',
35
- text: 'text',
36
- url: 'text'
37
- }
38
- };
18
+ const {doTest} = require('../procs/testaro');
39
19
 
40
20
  // FUNCTIONS
41
21
 
42
- // Returns the hover-related style properties of a trigger.
43
- const getHoverStyles = async loc => await loc.evaluate(element => {
44
- const {
45
- cursor,
46
- borderColor,
47
- borderStyle,
48
- borderWidth,
49
- outlineColor,
50
- outlineStyle,
51
- outlineWidth,
52
- outlineOffset,
53
- color,
54
- backgroundColor
55
- } = window.getComputedStyle(element);
56
- return {
57
- tagName: element.tagName,
58
- inputType: element.tagName === 'INPUT' ? element.getAttribute('type') || 'text' : null,
59
- cursor: cursor.replace(/^.+, */, ''),
60
- border: `${borderColor} ${borderStyle} ${borderWidth}`,
61
- outline: `${outlineColor} ${outlineStyle} ${outlineWidth} ${outlineOffset}`,
62
- color,
63
- backgroundColor
64
- };
65
- });
66
- // Returns data on the hover cursor.
67
- const getCursorData = hovStyles => {
68
- const {cursor, tagName} = hovStyles;
69
- const data = {
70
- cursor
71
- };
72
- // If the element is an input or a link:
73
- if (standardCursor[tagName]) {
74
- // If it is an input:
75
- if (tagName === 'INPUT') {
76
- // Get whether its hover cursor is standard.
77
- data.ok = [standardCursor.INPUT[hovStyles.inputType], 'default', 'auto'].includes(cursor);
78
- }
79
- // Otherwise, i.e. if it is a link:
80
- else {
81
- // Get whether its hover cursor is standard.
82
- data.ok = [standardCursor.A, 'auto'].includes(cursor);
83
- }
84
- }
85
- // Otherwise, if it is a button:
86
- else if (tagName === 'BUTTON') {
87
- // Get whether its hover cursor is standard.
88
- data.ok = ['default', 'auto'].includes(cursor);
89
- }
90
- // Otherwise, i.e. if it has another type and a hover listener:
91
- else {
92
- // Assume its hover cursor is standard.
93
- data.ok = true;
94
- }
95
- return data;
96
- };
97
- // Returns whether two hover styles are effectively identical.
98
- const areAlike = (styles0, styles1) => {
99
- // Return whether they are effectively identical.
100
- const areAlike = ['backgroundColor', 'border', 'color', 'outline']
101
- .every(style => styles1[style] === styles0[style]);
102
- return areAlike;
103
- };
104
- // Performs the hovInd test and reports results.
105
- exports.reporter = async (page, withItems, sampleSize = 20) => {
106
- // Initialize the result.
107
- const data = {
108
- typeTotals: {
109
- badCursor: 0,
110
- hoverLikeDefault: 0,
111
- hoverLikeFocus: 0
112
- }
113
- };
114
- const totals = [0, 0, 0, 0];
115
- const standardInstances = [];
116
- // Identify the triggers.
117
- const selectors = ['a', 'button', 'input', '[onmouseenter]', '[onmouseover]'];
118
- const selectorString = selectors.map(selector => `body ${selector}:visible`).join(', ');
119
- const locAll = page.locator(selectorString);
120
- const locsAll = await locAll.all();
121
- // Get the population-to-sample ratio.
122
- const psRatio = Math.max(1, locsAll.length / sampleSize);
123
- // Get a sample of the triggers.
124
- const sampleIndexes = getSample(locsAll, sampleSize);
125
- const sample = locsAll.filter((loc, index) => sampleIndexes.includes(index));
126
- // For each trigger:
127
- for (const loc of sample) {
128
- try {
129
- // Get its style properties.
130
- const preStyles = await getHoverStyles(loc);
131
- // Focus it.
132
- await loc.focus({timeout: 500});
133
- // If focusing succeeds, get its style properties.
134
- const focStyles = await getHoverStyles(loc);
135
- // Blur it.
136
- await loc.blur({timeout: 500});
137
- // If blurring succeeds, try to hover over it.
138
- await loc.hover({timeout: 600});
139
- // If hovering succeeds, get its style properties.
140
- const hovStyles = await getHoverStyles(loc);
141
- // If all 3 style declarations belong to the same element:
142
- if ([focStyles, hovStyles].every(style => style.code === preStyles.code)) {
143
- // Get data on the element if itemization is required.
144
- const elData = withItems ? await getLocatorData(loc) : null;
145
- // If the hover cursor is nonstandard:
146
- const cursorData = getCursorData(hovStyles);
147
- if (! cursorData.ok) {
148
- // Add to the totals.
149
- totals[2] += psRatio;
150
- data.typeTotals.badCursor += psRatio;
151
- // If itemization is required:
152
- if (withItems) {
153
- // Add an instance to the result.
154
- standardInstances.push({
155
- ruleID: 'hovInd',
156
- what: `Element has a nonstandard hover cursor (${cursorData.cursor})`,
157
- ordinalSeverity: 2,
158
- tagName: elData.tagName,
159
- id: elData.id,
160
- location: elData.location,
161
- excerpt: elData.excerpt
162
- });
163
- }
22
+ exports.reporter = async (page, withItems) => {
23
+ const getBadWhat = element => {
24
+ const violationTypes = [];
25
+ const isVisible = element.checkVisibility({
26
+ contentVisibilityAuto: true,
27
+ opacityProperty: true,
28
+ visibilityProperty: true
29
+ });
30
+ // If the element is visible:
31
+ if (isVisible) {
32
+ // Get its live style declaration.
33
+ const styleDec = window.getComputedStyle(element);
34
+ // FUNCTION DEFINITIONS START
35
+ // Returns hover-related style data on a trigger.
36
+ const getStyleData = () => {
37
+ const {
38
+ cursor,
39
+ borderColor,
40
+ borderStyle,
41
+ borderWidth,
42
+ outlineColor,
43
+ outlineStyle,
44
+ outlineWidth,
45
+ outlineOffset,
46
+ color,
47
+ backgroundColor
48
+ } = styleDec;
49
+ return {
50
+ tagName: element.tagName,
51
+ inputType: element.tagName === 'INPUT' ? element.getAttribute('type') || 'text' : null,
52
+ cursor: cursor.replace(/^.+, */, ''),
53
+ border: `${borderColor} ${borderStyle} ${borderWidth}`,
54
+ outline: `${outlineColor} ${outlineStyle} ${outlineWidth} ${outlineOffset}`,
55
+ color,
56
+ backgroundColor
57
+ };
58
+ };
59
+ // Returns whether the cursor is bad when the element is hovered over.
60
+ const cursorIsBad = hoverCursor => {
61
+ const {tagName, type} = element;
62
+ if (tagName === 'A' || tagName === 'INPUT' && type === 'image') {
63
+ return hoverCursor !== 'pointer';
164
64
  }
165
- // If the element is a button and the hover and default states are not distinct:
166
- if (hovStyles.tagName === 'BUTTON' && areAlike(preStyles, hovStyles)) {
167
- // Add to the totals.
168
- totals[1] += psRatio;
169
- data.typeTotals.hoverLikeDefault += psRatio;
170
- // If itemization is required:
171
- if (withItems) {
172
- // Add an instance to the result.
173
- standardInstances.push({
174
- ruleID: 'hovInd',
175
- what: 'Element border, outline, color, and background color do not change when hovered over',
176
- ordinalSeverity: 1,
177
- tagName: elData.tagName,
178
- id: elData.id,
179
- location: elData.location,
180
- excerpt: elData.excerpt
181
- });
182
- }
183
- }
184
- // If the hover and focus states are indistinct but differ from the default state:
185
- if (areAlike(hovStyles, focStyles) && ! areAlike(hovStyles, preStyles)) {
186
- // Add to the totals.
187
- totals[1] += psRatio;
188
- data.typeTotals.hoverLikeFocus += psRatio;
189
- // If itemization is required:
190
- if (withItems) {
191
- // Add an instance to the result.
192
- standardInstances.push({
193
- ruleID: 'hovInd',
194
- what: 'Element border, outline, color, and background color are alike on hover and focus',
195
- ordinalSeverity: 1,
196
- tagName: elData.tagName,
197
- id: elData.id,
198
- location: elData.location,
199
- excerpt: elData.excerpt
200
- });
65
+ if (tagName === 'INPUT') {
66
+ if (['button', 'radio', 'reset', 'submit'].some(typeName => type === typeName)) {
67
+ return hoverCursor !== 'default';
201
68
  }
69
+ return hoverCursor !== 'text';
202
70
  }
71
+ return ! ['auto', 'default'].includes(hoverCursor);
72
+ };
73
+ // Returns whether two hover styles are effectively identical.
74
+ const areAlike = (styles0, styles1) => {
75
+ // Return whether they are effectively identical.
76
+ const areAlike = ['cursor', 'backgroundColor', 'border', 'color', 'outline']
77
+ .every(style => styles1[style] === styles0[style]);
78
+ return areAlike;
79
+ };
80
+ // FUNCTION DEFINITIONS END
81
+ // Get its style data when neither focused nor hovered over.
82
+ const defaultStyleData = getStyleData();
83
+ // Correct the cursor value.
84
+ defaultStyleData.cursor = 'default';
85
+ // Get its style data when only focused.
86
+ element.focus();
87
+ const focusStyleData = getStyleData();
88
+ // Correct the cursor value.
89
+ focusStyleData.cursor = 'default';
90
+ // Get its style data when only hovered over.
91
+ element.blur();
92
+ element.dispatchEvent(new MouseEvent('mouseenter'));
93
+ const hoverStyleData = getStyleData();
94
+ const data = {};
95
+ // If the cursor is confusing when the element is only hovered over:
96
+ if (cursorIsBad(hoverStyleData.cursor)) {
97
+ // Add this to the violation types.
98
+ violationTypes.push(
99
+ `nonstandard mouse cursor (${hoverStyleData.cursor}) when hovered over`
100
+ );
203
101
  }
204
- // Otherwise, i.e. if the style properties do not all belong to the same element:
205
- else {
206
- // Report this and quit.
207
- data.prevented = true;
208
- data.error = 'ERROR: Page changes on focus or hover prevent test';
209
- break;
102
+ // If the neutral and hover styles are indistinguishable:
103
+ if (areAlike(defaultStyleData, hoverStyleData)) {
104
+ // Add this to the violation types.
105
+ violationTypes.push('normal and hover styles are indistinguishable');
106
+ // Add the details to the data.
107
+ data.n_h = {
108
+ neutral: defaultStyleData,
109
+ hover: hoverStyleData
110
+ };
210
111
  }
211
- }
212
- catch(error) {
213
- // If the page closed:
214
- if (
215
- ['Target page', 'detached', 'null', 'closed'].some(string => error.message.includes(string))
216
- ) {
217
- data.error = `ERROR during hovInd test: ${error.message}`;
112
+ // If the focus and hoverstyles are indistinguishable:
113
+ if (areAlike(focusStyleData, hoverStyleData)) {
114
+ // Add this to the violation types.
115
+ violationTypes.push('focus and hover styles are indistinguishable');
116
+ // Add the details to the data.
117
+ data.f_h = {
118
+ focus: focusStyleData,
119
+ hover: hoverStyleData
120
+ };
218
121
  }
219
- else {
220
- const elementText = loc ? await loc.textContent({timeout: 200}) : '';
221
- const excerpt = elementText ? elementText.trim().slice(0, 100) : '<no text>';
222
- data.error = `ERROR manipulating element (${excerpt}) during hovInd test`;
122
+ // If any violations occurred:
123
+ if (violationTypes.length) {
124
+ const description = `Element styles do not clearly indicate hovering: ${violationTypes.join('; ')}`;
125
+ // If there are additional data:
126
+ if (Object.keys(data).length) {
127
+ // Return the violation description and data.
128
+ return {
129
+ description,
130
+ data
131
+ };
132
+ }
133
+ // Otherwise, i.e. if there are no additional data:
134
+ else {
135
+ // Return the violation description.
136
+ return description;
137
+ }
223
138
  }
224
- data.prevented = true;
225
- // Abort this test.
226
- break;
227
- }
228
- }
229
- // Round the totals.
230
- Object.keys(data.typeTotals).forEach(rule => {
231
- data.typeTotals[rule] = Math.round(data.typeTotals[rule]);
232
- });
233
- for (const index in totals) {
234
- totals[index] = Math.round(totals[index]);
235
- }
236
- // If itemization is not required:
237
- if (! withItems) {
238
- // If any triggers have nonstandard hover cursors:
239
- if (data.typeTotals.badCursor) {
240
- // Add a summary instance to the result.
241
- standardInstances.push({
242
- ruleID: 'hovInd',
243
- what: 'Elements have nonstandard hover cursors',
244
- ordinalSeverity: 2,
245
- count: data.typeTotals.badCursor,
246
- tagName: '',
247
- id: '',
248
- location: {
249
- doc: '',
250
- type: '',
251
- spec: ''
252
- },
253
- excerpt: ''
254
- });
255
- }
256
- // If any triggers have hover styles not distinct from their default styles:
257
- if (data.typeTotals.hoverLikeDefault) {
258
- // Add a summary instance to the result.
259
- standardInstances.push({
260
- ruleID: 'hovInd',
261
- what: 'Element borders, outlines, and background colors do not change when hovered over',
262
- ordinalSeverity: 1,
263
- count: data.typeTotals.hoverLikeDefault,
264
- tagName: '',
265
- id: '',
266
- location: {
267
- doc: '',
268
- type: '',
269
- spec: ''
270
- },
271
- excerpt: ''
272
- });
273
- }
274
- // If any triggers have hover styles not distinct from their focus styles:
275
- if (data.typeTotals.hoverLikeFocus) {
276
- // Add a summary instance to the result.
277
- standardInstances.push({
278
- ruleID: 'hovInd',
279
- what: 'Element borders, outlines, and background colors on focus and on hover do not differ',
280
- ordinalSeverity: 1,
281
- count: data.typeTotals.hoverLikeFocus,
282
- tagName: '',
283
- id: '',
284
- location: {
285
- doc: '',
286
- type: '',
287
- spec: ''
288
- },
289
- excerpt: ''
290
- });
291
139
  }
292
- }
293
- // Return the result.
294
- return {
295
- data,
296
- totals,
297
- standardInstances
298
140
  };
141
+ const selector = 'a, button, input, [onmouseenter], [onmouseover]';
142
+ const whats = 'elements have confusing hover indicators';
143
+ return await doTest(page, withItems, 'hovInd', selector, whats, 1, null, getBadWhat.toString());
299
144
  };
package/testaro/motion.js CHANGED
@@ -51,6 +51,16 @@ exports.reporter = async page => {
51
51
  // Report this.
52
52
  data.prevented = true;
53
53
  data.error = 'Screenshot dimensions differ';
54
+ data.dimensions = {
55
+ shoot0: {
56
+ width: shoot0PNG.width,
57
+ height: shoot0PNG.height
58
+ },
59
+ shoot1: {
60
+ width: shoot1PNG.width,
61
+ height: shoot1PNG.height
62
+ }
63
+ }
54
64
  }
55
65
  // Otherwise, i.e. if their dimensions are identical:
56
66
  else {
package/tests/testaro.js CHANGED
@@ -576,7 +576,9 @@ exports.reporter = async (page, report, actIndex) => {
576
576
  // Round them.
577
577
  ruleResult.totals = ruleResult.totals.map(total => Math.round(total));
578
578
  }
579
- const ruleDataMiscKeys = Object.keys(ruleResult.data).filter(key => ! ['prevented', 'error'].includes(key));
579
+ const ruleDataMiscKeys = Object
580
+ .keys(ruleResult.data)
581
+ .filter(key => ! ['prevented', 'error'].includes(key));
580
582
  // For any other property of the rule report data object:
581
583
  ruleDataMiscKeys.forEach(key => {
582
584
  data.ruleData[ruleID] ??= {};