testaro 64.2.0 → 64.4.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/testaro.js +36 -14
- package/testaro/hovInd.js +115 -270
- package/testaro/motion.js +10 -0
- package/tests/nuVnu.js +54 -37
- package/tests/testaro.js +3 -1
package/package.json
CHANGED
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
|
|
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
|
|
67
|
-
let
|
|
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(
|
|
71
|
-
// Get it
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
window.getInstance(candidate, ruleID, ruleWhat, 1,
|
|
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
|
-
|
|
107
|
+
standardInstances.push(
|
|
86
108
|
window.getInstance(null, ruleID, whats, violationCount, severity, summaryTagName)
|
|
87
109
|
);
|
|
88
110
|
}
|
|
89
111
|
return {
|
|
90
|
-
data
|
|
91
|
-
totals
|
|
92
|
-
standardInstances
|
|
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
|
|
13
|
+
This test reports confusing hover indication.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
//
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
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/nuVnu.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
This tool subjects a page and its source to the Nu Html Checker, thereby testing scripted content found only in the loaded page and erroneous content before the browser corrects it. The API erratically replaces left and right double quotation marks with invalid UTF-8, which appears as 2 or 3 successive instances of the replacement character (U+fffd). Therefore, this test removes all such quotation marks and the replacement character. That causes 'Bad value “” for' to become 'Bad value for'. Since the corruption of quotation marks is erratic, no better solution is known.
|
|
13
|
-
This tool is the installed version of the Nu Html Checker. It is an alternative to the nuVal tool, which uses the same validator as a web service of the World Wide Web Consortium (W3C). Each tool has advantages and disadvantages. The main advantage of the nuVnu tool is that it can evaluate pages larger than about 80,000 bytes and pages reachable from the host that Testaro runs on even if not reachable from the public Internet. The main
|
|
13
|
+
This tool is the installed version of the Nu Html Checker. It is an alternative to the nuVal tool, which uses the same validator as a web service of the World Wide Web Consortium (W3C). Each tool has advantages and disadvantages. The main advantage of the nuVnu tool is that it can evaluate pages larger than about 80,000 bytes and pages reachable from the host that Testaro runs on even if not reachable from the public Internet. The main advantages of nuVal are that it usually runs faster than nuVnu and it does not require the Testaro host to provide a Java virtual machine.
|
|
14
|
+
When both nuVal and nuVnu are included in a job, nuVal should precede nuVnu. If nuVal succeeds, nuVnu aborts. So, only one of the two tools contributes instances to the job report.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
// IMPORTS
|
|
@@ -32,48 +33,64 @@ const tmpDir = os.tmpdir();
|
|
|
32
33
|
|
|
33
34
|
// Conducts and reports the Nu Html Checker tests.
|
|
34
35
|
exports.reporter = async (page, report, actIndex) => {
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// If any error was thrown:
|
|
51
|
-
catch (error) {
|
|
52
|
-
const errorMessage = error.message;
|
|
36
|
+
// Get the vuVal act, if it exists.
|
|
37
|
+
const nuValAct = report.acts.find(act => act.type === 'test' && act.which === 'nuVal');
|
|
38
|
+
// If it does not exist or it exists but was prevented:
|
|
39
|
+
if (! nuValAct || nuValAct.data?.prevented) {
|
|
40
|
+
const act = report.acts[actIndex];
|
|
41
|
+
const {rules, withSource} = act;
|
|
42
|
+
// Get the content.
|
|
43
|
+
const data = await getContent(page, withSource);
|
|
44
|
+
let result;
|
|
45
|
+
// If it was obtained:
|
|
46
|
+
if (data.testTarget) {
|
|
47
|
+
const pagePath = `${tmpDir}/nuVnu-page-${report.id}.html`;
|
|
48
|
+
// Save it in a temporary file.
|
|
49
|
+
await fs.writeFile(pagePath, data.testTarget);
|
|
50
|
+
let nuData;
|
|
53
51
|
try {
|
|
54
|
-
|
|
55
|
-
nuData =
|
|
52
|
+
// Get Nu Html Checker output on it.
|
|
53
|
+
nuData = await vnu.check(['--format', 'json', '--stdout', pagePath]);
|
|
56
54
|
}
|
|
57
|
-
// If
|
|
55
|
+
// If any error was thrown:
|
|
58
56
|
catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
const errorMessage = error.message;
|
|
58
|
+
try {
|
|
59
|
+
// Parse it as JSON, i.e. a benign nuVnu result with at least 1 violation.
|
|
60
|
+
nuData = JSON.parse(error.message);
|
|
61
|
+
}
|
|
62
|
+
// If parsing it as JSON fails:
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Report a genuine error.
|
|
65
|
+
data.prevented = true;
|
|
66
|
+
data.error = errorMessage;
|
|
67
|
+
}
|
|
62
68
|
}
|
|
69
|
+
// Delete the temporary file.
|
|
70
|
+
await fs.unlink(pagePath);
|
|
71
|
+
// Postprocess the result.
|
|
72
|
+
result = curate(data, nuData, rules);
|
|
73
|
+
}
|
|
74
|
+
// Otherwise, i.e. if the content was not obtained:
|
|
75
|
+
else {
|
|
76
|
+
// Report this.
|
|
77
|
+
data.prevented = true;
|
|
78
|
+
data.error = 'Content not obtained';
|
|
63
79
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
return {
|
|
81
|
+
data,
|
|
82
|
+
result
|
|
83
|
+
};
|
|
68
84
|
}
|
|
69
|
-
// Otherwise, i.e. if the
|
|
85
|
+
// Otherwise, i.e. if the nuVal act exists and succeeded:
|
|
70
86
|
else {
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
// Abort this act and report this.
|
|
88
|
+
return {
|
|
89
|
+
data: {
|
|
90
|
+
skipped: true,
|
|
91
|
+
reason: 'nuVal succeeded'
|
|
92
|
+
},
|
|
93
|
+
result: {}
|
|
94
|
+
};
|
|
74
95
|
}
|
|
75
|
-
return {
|
|
76
|
-
data,
|
|
77
|
-
result
|
|
78
|
-
};
|
|
79
96
|
};
|
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
|
|
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] ??= {};
|