testaro 13.0.2 → 14.1.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/README.md +190 -94
- package/actSpecs.js +17 -158
- package/package.json +1 -1
- package/run.js +31 -31
- package/standardize.js +338 -0
- package/{tests → testaro}/allHidden.js +36 -1
- package/{tests → testaro}/attVal.js +36 -2
- package/{tests → testaro}/autocomplete.js +34 -1
- package/{tests → testaro}/bulk.js +15 -1
- package/{tests → testaro}/docType.js +15 -1
- package/{tests → testaro}/dupAtt.js +34 -1
- package/{tests → testaro}/elements.js +9 -3
- package/testaro/embAc.js +78 -0
- package/{tests → testaro}/filter.js +35 -1
- package/{tests → testaro}/focAll.js +20 -3
- package/{tests → testaro}/focInd.js +45 -3
- package/{tests → testaro}/focOp.js +61 -2
- package/{tests → testaro}/focVis.js +35 -1
- package/{tests → testaro}/hover.js +67 -5
- package/{tests → testaro}/labClash.js +54 -4
- package/{tests → testaro}/linkTo.js +33 -1
- package/{tests → testaro}/linkUl.js +43 -5
- package/{tests → testaro}/menuNav.js +42 -1
- package/{tests → testaro}/miniText.js +32 -1
- package/{tests → testaro}/motion.js +27 -4
- package/{tests → testaro}/nonTable.js +32 -1
- package/{tests → testaro}/radioSet.js +33 -2
- package/{tests → testaro}/role.js +38 -1
- package/{tests → testaro}/styleDiff.js +43 -2
- package/{tests → testaro}/tabNav.js +42 -1
- package/{tests → testaro}/textNodes.js +6 -2
- package/{tests → testaro}/title.js +8 -4
- package/{tests → testaro}/titledEl.js +32 -1
- package/{tests → testaro}/zIndex.js +40 -2
- package/tests/alfa.js +72 -75
- package/tests/continuum.js +6 -2
- package/tests/ibm.js +14 -42
- package/tests/testaro.js +73 -0
- package/validation/tests/jobs/allHidden.json +877 -174
- package/validation/tests/jobs/attVal.json +57 -19
- package/validation/tests/jobs/autocomplete.json +34 -8
- package/validation/tests/jobs/bulk.json +33 -7
- package/validation/tests/jobs/docType.json +23 -5
- package/validation/tests/jobs/dupAtt.json +47 -8
- package/validation/tests/jobs/elements.json +231 -70
- package/validation/tests/jobs/embAc.json +70 -15
- package/validation/tests/jobs/filter.json +56 -12
- package/validation/tests/jobs/focAll.json +64 -16
- package/validation/tests/jobs/focInd.json +107 -23
- package/validation/tests/jobs/focOp.json +93 -21
- package/validation/tests/jobs/focVis.json +16 -4
- package/validation/tests/jobs/hover.json +246 -56
- package/validation/tests/jobs/labClash.json +43 -11
- package/validation/tests/jobs/linkTo.json +16 -4
- package/validation/tests/jobs/linkUl.json +79 -19
- package/validation/tests/jobs/menuNav.json +313 -65
- package/validation/tests/jobs/miniText.json +21 -5
- package/validation/tests/jobs/motion.json +81 -23
- package/validation/tests/jobs/nonTable.json +26 -6
- package/validation/tests/jobs/radioSet.json +43 -11
- package/validation/tests/jobs/role.json +93 -19
- package/validation/tests/jobs/styleDiff.json +124 -28
- package/validation/tests/jobs/tabNav.json +313 -65
- package/validation/tests/jobs/textNodes.json +190 -49
- package/validation/tests/jobs/title.json +23 -5
- package/validation/tests/jobs/titledEl.json +26 -6
- package/validation/tests/jobs/zIndex.json +28 -8
- package/validation/tests/old/allHidden.json +314 -0
- package/validation/tests/old/attVal.json +60 -0
- package/validation/tests/old/autocomplete.json +51 -0
- package/validation/tests/old/bulk.json +48 -0
- package/validation/tests/old/docType.json +46 -0
- package/validation/tests/old/dupAtt.json +51 -0
- package/validation/tests/old/elements.json +140 -0
- package/validation/tests/old/embAc.json +54 -0
- package/validation/tests/old/filter.json +55 -0
- package/validation/tests/old/focAll.json +68 -0
- package/validation/tests/old/focInd.json +69 -0
- package/validation/tests/old/focOp.json +62 -0
- package/validation/tests/old/focVis.json +35 -0
- package/validation/tests/old/hover.json +118 -0
- package/validation/tests/old/labClash.json +52 -0
- package/validation/tests/old/linkTo.json +35 -0
- package/validation/tests/old/linkUl.json +71 -0
- package/validation/tests/old/menuNav.json +106 -0
- package/validation/tests/old/miniText.json +36 -0
- package/validation/tests/old/motion.json +62 -0
- package/validation/tests/old/nonTable.json +37 -0
- package/validation/tests/old/radioSet.json +52 -0
- package/validation/tests/old/role.json +60 -0
- package/validation/tests/old/styleDiff.json +71 -0
- package/validation/tests/old/tabNav.json +106 -0
- package/validation/tests/old/temp.js +28 -0
- package/validation/tests/old/textNodes.json +98 -0
- package/validation/tests/old/title.json +46 -0
- package/validation/tests/old/titledEl.json +37 -0
- package/validation/tests/old/zIndex.json +49 -0
- package/validation/tests/targets/attVal/good.html +1 -1
- package/validation/tests/targets/elements/index.html +1 -0
- package/tests/embAc.js +0 -36
package/standardize.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/*
|
|
2
|
+
standardize.js
|
|
3
|
+
Converts test results to the standard format.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## FUNCTIONS
|
|
7
|
+
|
|
8
|
+
// Limits the length of and unilinearizes a string.
|
|
9
|
+
const cap = rawString => {
|
|
10
|
+
const string = rawString.replace(/\s+/g, ' ');
|
|
11
|
+
if (string && string.length > 400) {
|
|
12
|
+
return `${string.slice(0, 200)} ... ${string.slice(-200)}`;
|
|
13
|
+
}
|
|
14
|
+
else if (string) {
|
|
15
|
+
return string;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// Converts issue instances at an axe certainty level.
|
|
22
|
+
const doAxe = (result, standardResult, certainty) => {
|
|
23
|
+
if (result.details && result.details[certainty]) {
|
|
24
|
+
result.details[certainty].forEach(rule => {
|
|
25
|
+
rule.nodes.forEach(node => {
|
|
26
|
+
const whatSet = new Set([
|
|
27
|
+
rule.help,
|
|
28
|
+
... node.any.map(anyItem => anyItem.message),
|
|
29
|
+
... node.all.map(allItem => allItem.message)
|
|
30
|
+
]);
|
|
31
|
+
const initialSeverity = ['minor', 'moderate', 'serious', 'critical'].indexOf(node.impact);
|
|
32
|
+
const moreSeverity = certainty === 'violations' ? 4 : 0;
|
|
33
|
+
const instance = {
|
|
34
|
+
issueID: rule.id,
|
|
35
|
+
what: Array.from(whatSet.values()).join('; '),
|
|
36
|
+
ordinalSeverity: initialSeverity + moreSeverity,
|
|
37
|
+
location: {
|
|
38
|
+
doc: 'dom',
|
|
39
|
+
type: 'selector',
|
|
40
|
+
spec: node.target && node.target.length ? node.target[0] : ''
|
|
41
|
+
},
|
|
42
|
+
excerpt: cap(node.html)
|
|
43
|
+
};
|
|
44
|
+
standardResult.instances.push(instance);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
// Converts issue instances at an htmlcs severity level.
|
|
50
|
+
const doHTMLCS = (result, standardResult, severity) => {
|
|
51
|
+
if (result[severity]) {
|
|
52
|
+
Object.keys(result[severity]).forEach(ruleID => {
|
|
53
|
+
const ruleData = result[severity][ruleID];
|
|
54
|
+
Object.keys(ruleData).forEach(what => {
|
|
55
|
+
ruleData[what].forEach(item => {
|
|
56
|
+
const {tagName, code} = item;
|
|
57
|
+
const instance = {
|
|
58
|
+
issueID: ruleID,
|
|
59
|
+
what,
|
|
60
|
+
ordinalSeverity: ['Warning', 'Error'].indexOf(severity),
|
|
61
|
+
location: {
|
|
62
|
+
doc: 'dom',
|
|
63
|
+
type: '',
|
|
64
|
+
spec: ''
|
|
65
|
+
},
|
|
66
|
+
excerpt: cap(`${tagName ? tagName + ': ' : ''}${code || ''}`)
|
|
67
|
+
};
|
|
68
|
+
standardResult.instances.push(instance);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
// Converts issue instances from a nuVal document type.
|
|
75
|
+
const doNuVal = (result, standardResult, docType) => {
|
|
76
|
+
const items = result[docType] && result[docType].messages;
|
|
77
|
+
if (items && items.length) {
|
|
78
|
+
items.forEach(item => {
|
|
79
|
+
const instance = {
|
|
80
|
+
issueID: item.message,
|
|
81
|
+
what: item.message,
|
|
82
|
+
ordinalSeverity: -1,
|
|
83
|
+
location: {
|
|
84
|
+
doc: docType === 'pageContent' ? 'dom' : 'source',
|
|
85
|
+
type: 'line',
|
|
86
|
+
spec: item.lastLine.toString()
|
|
87
|
+
},
|
|
88
|
+
excerpt: cap(item.extract)
|
|
89
|
+
};
|
|
90
|
+
const {type, subType} = item;
|
|
91
|
+
if (type === 'info' && subType === 'warning') {
|
|
92
|
+
instance.ordinalSeverity = 0;
|
|
93
|
+
}
|
|
94
|
+
else if (type === 'error') {
|
|
95
|
+
instance.ordinalSeverity = subType === 'fatal' ? 2 : 1;
|
|
96
|
+
}
|
|
97
|
+
standardResult.instances.push(instance);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
// Converts instances of a qualWeb rule class.
|
|
102
|
+
const doQualWeb = (result, standardResult, ruleClassName) => {
|
|
103
|
+
if (result.modules && result.modules[ruleClassName]) {
|
|
104
|
+
const ruleClass = result.modules[ruleClassName];
|
|
105
|
+
let classSeverity = 0;
|
|
106
|
+
if (ruleClass.metadata) {
|
|
107
|
+
classSeverity = 2 * [
|
|
108
|
+
'best-practices', 'wcag-techniques', 'act-rules'
|
|
109
|
+
].indexOf(ruleClassName);
|
|
110
|
+
standardResult.totals[classSeverity] += ruleClass.metadata.warning;
|
|
111
|
+
standardResult.totals[classSeverity + 1] += ruleClass.metadata.failed;
|
|
112
|
+
}
|
|
113
|
+
Object.keys(ruleClass.assertions).forEach(rule => {
|
|
114
|
+
ruleClass.assertions[rule].results.forEach(item => {
|
|
115
|
+
item.elements.forEach(element => {
|
|
116
|
+
const instance = {
|
|
117
|
+
issueID: ruleClass.assertions[rule].name,
|
|
118
|
+
what: ruleClass.assertions[rule].description,
|
|
119
|
+
ordinalSeverity: classSeverity + (item.verdict === 'failed' ? 1 : 0),
|
|
120
|
+
location: {
|
|
121
|
+
doc: 'dom',
|
|
122
|
+
type: 'selector',
|
|
123
|
+
spec: element.pointer
|
|
124
|
+
},
|
|
125
|
+
excerpt: cap(element.htmlCode)
|
|
126
|
+
};
|
|
127
|
+
standardResult.instances.push(instance);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
// Converts instances of a wave rule category.
|
|
134
|
+
const doWAVE = (result, standardResult, categoryName) => {
|
|
135
|
+
if (result.categories && result.categories[categoryName]) {
|
|
136
|
+
const category = result.categories[categoryName];
|
|
137
|
+
const ordinalSeverity = categoryName === 'alert' ? 0 : 1;
|
|
138
|
+
Object.keys(category.items).forEach(rule => {
|
|
139
|
+
category.items[rule].selectors.forEach(selector => {
|
|
140
|
+
const instance = {
|
|
141
|
+
issueID: rule,
|
|
142
|
+
what: category.items[rule].description,
|
|
143
|
+
ordinalSeverity,
|
|
144
|
+
location: {
|
|
145
|
+
doc: 'dom',
|
|
146
|
+
type: 'selector',
|
|
147
|
+
spec: selector
|
|
148
|
+
},
|
|
149
|
+
excerpt: ''
|
|
150
|
+
};
|
|
151
|
+
standardResult.instances.push(instance);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
// Converts a result.
|
|
157
|
+
const convert = (testName, result, standardResult) => {
|
|
158
|
+
// alfa
|
|
159
|
+
if (testName === 'alfa' && result.totals) {
|
|
160
|
+
standardResult.totals = [result.totals.warnings, result.totals.failures];
|
|
161
|
+
result.items.forEach(item => {
|
|
162
|
+
const instance = {
|
|
163
|
+
issueID: item.rule.ruleID,
|
|
164
|
+
what: item.rule.ruleSummary,
|
|
165
|
+
ordinalSeverity: ['cantTell', 'failed'].indexOf(item.verdict),
|
|
166
|
+
location: {
|
|
167
|
+
doc: 'dom',
|
|
168
|
+
type: 'xpath',
|
|
169
|
+
spec: item.target.path
|
|
170
|
+
},
|
|
171
|
+
excerpt: cap(item.target.codeLines.join(' '))
|
|
172
|
+
};
|
|
173
|
+
standardResult.instances.push(instance);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// axe
|
|
177
|
+
else if (
|
|
178
|
+
testName === 'axe'
|
|
179
|
+
&& result.totals
|
|
180
|
+
&& (result.totals.rulesWarned || result.totals.rulesViolated)
|
|
181
|
+
) {
|
|
182
|
+
const {totals} = result;
|
|
183
|
+
standardResult.totals = [
|
|
184
|
+
totals.warnings.minor,
|
|
185
|
+
totals.warnings.moderate,
|
|
186
|
+
totals.warnings.serious,
|
|
187
|
+
totals.warnings.critical,
|
|
188
|
+
totals.violations.minor,
|
|
189
|
+
totals.violations.moderate,
|
|
190
|
+
totals.violations.serious,
|
|
191
|
+
totals.violations.critical
|
|
192
|
+
];
|
|
193
|
+
doAxe(result, standardResult, 'incomplete');
|
|
194
|
+
doAxe(result, standardResult, 'violations');
|
|
195
|
+
}
|
|
196
|
+
// continuum
|
|
197
|
+
else if (testName === 'continuum' && Array.isArray(result) && result.length) {
|
|
198
|
+
standardResult.totals = [result.length];
|
|
199
|
+
result.forEach(item => {
|
|
200
|
+
const instance = {
|
|
201
|
+
issueID: item.engineTestId.toString(),
|
|
202
|
+
what: item.attributeDetail,
|
|
203
|
+
ordinalSeverity: 0,
|
|
204
|
+
location: {
|
|
205
|
+
doc: 'dom',
|
|
206
|
+
type: 'selector',
|
|
207
|
+
spec: item.path
|
|
208
|
+
},
|
|
209
|
+
excerpt: item.element
|
|
210
|
+
};
|
|
211
|
+
standardResult.instances.push(instance);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// htmlcs
|
|
215
|
+
else if (testName === 'htmlcs' && result) {
|
|
216
|
+
doHTMLCS(result, standardResult, 'Warning');
|
|
217
|
+
doHTMLCS(result, standardResult, 'Error');
|
|
218
|
+
const {instances} = standardResult;
|
|
219
|
+
standardResult.totals = [
|
|
220
|
+
instances.filter(instance => instance.ordinalSeverity === 0).length,
|
|
221
|
+
instances.filter(instance => instance.ordinalSeverity === 1).length
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
// ibm
|
|
225
|
+
else if (testName === 'ibm' && result.totals) {
|
|
226
|
+
standardResult.totals = [result.totals.recommendation, result.totals.violation];
|
|
227
|
+
result.items.forEach(item => {
|
|
228
|
+
const instance = {
|
|
229
|
+
issueID: item.ruleId,
|
|
230
|
+
what: item.message,
|
|
231
|
+
ordinalSeverity: ['recommendation', 'violation'].indexOf(item.level),
|
|
232
|
+
location: {
|
|
233
|
+
doc: 'dom',
|
|
234
|
+
type: 'xpath',
|
|
235
|
+
spec: item.path.dom
|
|
236
|
+
},
|
|
237
|
+
excerpt: cap(item.snippet)
|
|
238
|
+
};
|
|
239
|
+
standardResult.instances.push(instance);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// nuVal
|
|
243
|
+
else if (testName === 'nuVal' && (result.pageContent || result.rawPage)) {
|
|
244
|
+
if (result.pageContent) {
|
|
245
|
+
doNuVal(result, standardResult, 'pageContent');
|
|
246
|
+
}
|
|
247
|
+
if (result.rawPage) {
|
|
248
|
+
doNuVal(result, standardResult, 'rawPage');
|
|
249
|
+
}
|
|
250
|
+
const {instances} = standardResult;
|
|
251
|
+
standardResult.totals = [
|
|
252
|
+
instances.filter(instance => instance.ordinalSeverity === 0).length,
|
|
253
|
+
instances.filter(instance => instance.ordinalSeverity === 1).length,
|
|
254
|
+
instances.filter(instance => instance.ordinalSeverity === 2).length
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
// qualWeb
|
|
258
|
+
else if (
|
|
259
|
+
testName === 'qualWeb'
|
|
260
|
+
&& result.modules
|
|
261
|
+
&& (
|
|
262
|
+
result.modules['act-rules']
|
|
263
|
+
|| result.modules['wcag-techniques']
|
|
264
|
+
|| result.modules['best-practices']
|
|
265
|
+
)
|
|
266
|
+
) {
|
|
267
|
+
standardResult.totals = [0, 0, 0, 0, 0, 0];
|
|
268
|
+
if (result.modules['act-rules']) {
|
|
269
|
+
doQualWeb(result, standardResult, 'act-rules');
|
|
270
|
+
}
|
|
271
|
+
if (result.modules['wcag-techniques']) {
|
|
272
|
+
doQualWeb(result, standardResult, 'wcag-techniques');
|
|
273
|
+
}
|
|
274
|
+
if (result.modules['best-practices']) {
|
|
275
|
+
doQualWeb(result, standardResult, 'best-practices');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// tenon
|
|
279
|
+
else if (testName === 'tenon' && result.data && result.data.resultSet) {
|
|
280
|
+
result.data.resultSet.forEach(item => {
|
|
281
|
+
const instance = {
|
|
282
|
+
issueID: item.tID ? item.tID.toString() : '',
|
|
283
|
+
what: item.errorTitle || '',
|
|
284
|
+
ordinalSeverity: Math.min(
|
|
285
|
+
5, Math.max(0, Math.round((item.certainty || 0) * (item.priority || 0) / 2000))
|
|
286
|
+
),
|
|
287
|
+
location: {
|
|
288
|
+
doc: 'dom',
|
|
289
|
+
type: 'xpath',
|
|
290
|
+
spec: item.xpath || ''
|
|
291
|
+
},
|
|
292
|
+
excerpt: cap(item.errorSnippet || '')
|
|
293
|
+
};
|
|
294
|
+
standardResult.instances.push(instance);
|
|
295
|
+
});
|
|
296
|
+
standardResult.totals = [0, 0, 0, 0, 0, 0];
|
|
297
|
+
standardResult.instances.forEach(instance => {
|
|
298
|
+
standardResult.totals[instance.ordinalSeverity]++;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// testaro
|
|
302
|
+
else if (testName === 'testaro') {
|
|
303
|
+
const rules = Object.keys(result.rules);
|
|
304
|
+
standardResult.totals = [0, 0, 0, 0];
|
|
305
|
+
rules.forEach(rule => {
|
|
306
|
+
const ruleResult = result.rules[rule];
|
|
307
|
+
standardResult.totals.forEach((total, index) => {
|
|
308
|
+
standardResult.totals[index] += ruleResult.totals[index];
|
|
309
|
+
});
|
|
310
|
+
standardResult.instances.push(... ruleResult.standardInstances);
|
|
311
|
+
});
|
|
312
|
+
standardResult.totals = standardResult.totals.map(total => Math.round(total));
|
|
313
|
+
}
|
|
314
|
+
// wave
|
|
315
|
+
else if (
|
|
316
|
+
testName === 'wave'
|
|
317
|
+
&& result.categories
|
|
318
|
+
&& (
|
|
319
|
+
result.categories.error
|
|
320
|
+
|| result.categories.contrast
|
|
321
|
+
|| result.categories.alert
|
|
322
|
+
)
|
|
323
|
+
) {
|
|
324
|
+
const {categories} = result;
|
|
325
|
+
standardResult.totals = [
|
|
326
|
+
categories.alert.count || 0, (categories.error.count || 0) + (categories.contrast.count || 0)
|
|
327
|
+
];
|
|
328
|
+
['error', 'contrast', 'alert'].forEach(categoryName => {
|
|
329
|
+
doWAVE(result, standardResult, categoryName);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
// Converts the results.
|
|
334
|
+
exports.standardize = act => {
|
|
335
|
+
const {which} = act;
|
|
336
|
+
const {result, standardResult} = act;
|
|
337
|
+
convert(which, result, standardResult);
|
|
338
|
+
};
|
|
@@ -45,5 +45,40 @@ exports.reporter = async page => {
|
|
|
45
45
|
});
|
|
46
46
|
return data;
|
|
47
47
|
});
|
|
48
|
-
|
|
48
|
+
const standardInstances = [];
|
|
49
|
+
const reportables = {
|
|
50
|
+
hidden: [0, 'hidden'],
|
|
51
|
+
reallyHidden: [1, 'effectively hidden'],
|
|
52
|
+
visHidden: [0, 'visually hidden'],
|
|
53
|
+
ariaHidden: [1, 'hidden by ARIA'],
|
|
54
|
+
document: [1, 'Document', 'document.documentElement'],
|
|
55
|
+
body: [1, 'Document body', 'document.body'],
|
|
56
|
+
main: [0, 'main region', 'main, [role="main"]']
|
|
57
|
+
};
|
|
58
|
+
['document', 'body', 'main'].forEach(region => {
|
|
59
|
+
['hidden', 'reallyHidden', 'visHidden', 'ariaHidden'].forEach(hider => {
|
|
60
|
+
if (data[hider][region]) {
|
|
61
|
+
standardInstances.push({
|
|
62
|
+
issueID: `allHidden-${hider}-${region}`,
|
|
63
|
+
what: `${reportables[region][1]} ${reportables[hider][1]}`,
|
|
64
|
+
ordinalSeverity: reportables[region][0] + reportables[hider][0] || 0,
|
|
65
|
+
location: {
|
|
66
|
+
doc: 'dom',
|
|
67
|
+
type: 'selector',
|
|
68
|
+
spec: reportables[region][2]
|
|
69
|
+
},
|
|
70
|
+
excerpt: ''
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
const totals = [0, 0, 0, 0];
|
|
76
|
+
standardInstances.forEach(instance => {
|
|
77
|
+
totals[instance.ordinalSeverity]++;
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
data,
|
|
81
|
+
totals,
|
|
82
|
+
standardInstances
|
|
83
|
+
};
|
|
49
84
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
attVal
|
|
3
3
|
This test reports attributes with illicit values.
|
|
4
4
|
*/
|
|
5
|
-
exports.reporter = async (page, attributeName, areLicit, values
|
|
5
|
+
exports.reporter = async (page, withItems, attributeName, areLicit, values) => {
|
|
6
|
+
console.log('Starting reporter');
|
|
6
7
|
// Identify the elements that have the specified attribute with illicit values.
|
|
7
8
|
const badAttributeData = await page.evaluate(
|
|
8
9
|
async args => {
|
|
@@ -41,8 +42,41 @@ exports.reporter = async (page, attributeName, areLicit, values, withItems) => {
|
|
|
41
42
|
const data = {
|
|
42
43
|
total: badAttributeData.length
|
|
43
44
|
};
|
|
45
|
+
const standardInstances = [];
|
|
44
46
|
if (withItems) {
|
|
45
47
|
data.items = badAttributeData;
|
|
48
|
+
badAttributeData.forEach(item => {
|
|
49
|
+
standardInstances.push({
|
|
50
|
+
issueID: `attVal-${item.tagName}-${attributeName}`,
|
|
51
|
+
what:
|
|
52
|
+
`${item.tagName} element has attribute ${attributeName} with illicit value ${item.attributeValue}`,
|
|
53
|
+
ordinalSeverity: 2,
|
|
54
|
+
location: {
|
|
55
|
+
doc: '',
|
|
56
|
+
type: '',
|
|
57
|
+
spec: ''
|
|
58
|
+
},
|
|
59
|
+
excerpt: item.textStart
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (data.total) {
|
|
64
|
+
standardInstances.push({
|
|
65
|
+
issueID: 'attVal',
|
|
66
|
+
what: 'Elements have attributes with illicit values',
|
|
67
|
+
ordinalSeverity: 2,
|
|
68
|
+
location: {
|
|
69
|
+
doc: '',
|
|
70
|
+
type: '',
|
|
71
|
+
spec: ''
|
|
72
|
+
},
|
|
73
|
+
excerpt: ''
|
|
74
|
+
});
|
|
46
75
|
}
|
|
47
|
-
|
|
76
|
+
const totals = [0, 0, data.total, 0];
|
|
77
|
+
return {
|
|
78
|
+
data,
|
|
79
|
+
totals,
|
|
80
|
+
standardInstances
|
|
81
|
+
};
|
|
48
82
|
};
|
|
@@ -70,6 +70,39 @@ exports.reporter = async (page, withItems) => {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
const standardInstances = [];
|
|
74
|
+
if (data.items) {
|
|
75
|
+
data.items.forEach(item => {
|
|
76
|
+
standardInstances.push({
|
|
77
|
+
issueID: `autocomplete-${item[0]}`,
|
|
78
|
+
what: `Input is missing the required autocomplete attribute with value ${item[0]}`,
|
|
79
|
+
ordinalSeverity: 2,
|
|
80
|
+
location: {
|
|
81
|
+
doc: '',
|
|
82
|
+
type: '',
|
|
83
|
+
spec: ''
|
|
84
|
+
},
|
|
85
|
+
excerpt: item[1]
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (data.total) {
|
|
90
|
+
standardInstances.push({
|
|
91
|
+
issueID: 'autocomplete',
|
|
92
|
+
what: 'Inputs are missing required autocomplete attributes',
|
|
93
|
+
ordinalSeverity: 2,
|
|
94
|
+
location: {
|
|
95
|
+
doc: '',
|
|
96
|
+
type: '',
|
|
97
|
+
spec: ''
|
|
98
|
+
},
|
|
99
|
+
excerpt: ''
|
|
100
|
+
});
|
|
101
|
+
}
|
|
73
102
|
// Return the data.
|
|
74
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
data,
|
|
105
|
+
totals: [0, 0, data.total, 0],
|
|
106
|
+
standardInstances
|
|
107
|
+
};
|
|
75
108
|
};
|
|
@@ -18,5 +18,19 @@ exports.reporter = async page => {
|
|
|
18
18
|
});
|
|
19
19
|
const visibleElements = await page.$$('body :visible');
|
|
20
20
|
data.visibleElements = visibleElements.length;
|
|
21
|
-
return {
|
|
21
|
+
return {
|
|
22
|
+
data,
|
|
23
|
+
totals: [Math.round(data.visibleElements / 400), 0, 0, 0],
|
|
24
|
+
standardInstances: data.visibleElements < 200 ? [] : [{
|
|
25
|
+
issueID: 'bulk',
|
|
26
|
+
what: 'Page contains a large number of visible elements',
|
|
27
|
+
ordinalSeverity: 0,
|
|
28
|
+
location: {
|
|
29
|
+
doc: '',
|
|
30
|
+
type: '',
|
|
31
|
+
spec: ''
|
|
32
|
+
},
|
|
33
|
+
excerpt: ''
|
|
34
|
+
}]
|
|
35
|
+
};
|
|
22
36
|
};
|
|
@@ -10,5 +10,19 @@ exports.reporter = async page => {
|
|
|
10
10
|
const docHasType = !! docType && docType.name && docType.name.toLowerCase() === 'html';
|
|
11
11
|
return docHasType;
|
|
12
12
|
});
|
|
13
|
-
return {
|
|
13
|
+
return {
|
|
14
|
+
data: {docHasType},
|
|
15
|
+
totals: [0, 0, 0, docHasType ? 0 : 1],
|
|
16
|
+
standardInstances: docHasType ? [] : [{
|
|
17
|
+
issueID: 'docType',
|
|
18
|
+
what: 'Document has no standard HTML doctype preamble',
|
|
19
|
+
ordinalSeverity: 3,
|
|
20
|
+
location: {
|
|
21
|
+
doc: '',
|
|
22
|
+
type: '',
|
|
23
|
+
spec: ''
|
|
24
|
+
},
|
|
25
|
+
excerpt: ''
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
14
28
|
};
|
|
@@ -76,6 +76,39 @@ exports.reporter = async (page, withItems) => {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
const standardInstances = [];
|
|
80
|
+
if (data.items) {
|
|
81
|
+
data.items.forEach(item => {
|
|
82
|
+
standardInstances.push({
|
|
83
|
+
issueID: 'dupAtt',
|
|
84
|
+
what: `Element ${item.tagName} has 2 attributes with the same name`,
|
|
85
|
+
ordinalSeverity: 2,
|
|
86
|
+
location: {
|
|
87
|
+
doc: '',
|
|
88
|
+
type: '',
|
|
89
|
+
spec: ''
|
|
90
|
+
},
|
|
91
|
+
excerpt: item.attributes.join(' ... ')
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (data.total) {
|
|
96
|
+
standardInstances.push({
|
|
97
|
+
issueID: 'dupAtt',
|
|
98
|
+
what: 'In some elements 2 attributes have the same name',
|
|
99
|
+
ordinalSeverity: 2,
|
|
100
|
+
location: {
|
|
101
|
+
doc: '',
|
|
102
|
+
type: '',
|
|
103
|
+
spec: ''
|
|
104
|
+
},
|
|
105
|
+
excerpt: ''
|
|
106
|
+
});
|
|
107
|
+
}
|
|
79
108
|
// Return the data.
|
|
80
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
data,
|
|
111
|
+
totals: [0, 0, data.total, 0],
|
|
112
|
+
standardInstances
|
|
113
|
+
};
|
|
81
114
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/*
|
|
2
2
|
elements
|
|
3
|
-
This test reports data about specified elements.
|
|
3
|
+
This test reports data about specified elements within the document body.
|
|
4
4
|
Meanings of detailLevel values:
|
|
5
5
|
0. Only total element count; no detail.
|
|
6
6
|
1. Also data on each specified element.
|
|
7
7
|
2. Data on each specified element also include the text content of the parent element.
|
|
8
8
|
3. Data on each specified element also include data on its sibling nodes.
|
|
9
9
|
*/
|
|
10
|
-
exports.reporter = async (
|
|
10
|
+
exports.reporter = async (
|
|
11
|
+
page, withItems, detailLevel = 0, tagName = null, onlyVisible = false, attribute
|
|
12
|
+
) => {
|
|
11
13
|
// Determine a selector of the specified elements, including any descendants of open shadow roots.
|
|
12
14
|
let selector = `body ${tagName ? tagName.toLowerCase() : '*'}`;
|
|
13
15
|
if (attribute) {
|
|
@@ -155,5 +157,9 @@ exports.reporter = async (page, detailLevel, tagName, onlyVisible, attribute) =>
|
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
// Return the result.
|
|
158
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
data,
|
|
162
|
+
totals: [],
|
|
163
|
+
standardInstances: []
|
|
164
|
+
};
|
|
159
165
|
};
|
package/testaro/embAc.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
embAc
|
|
3
|
+
This test reports interactive elements (links, buttons, inputs, and select lists)
|
|
4
|
+
contained by links or buttons. Such embedding not only violates the HTML standard,
|
|
5
|
+
but also complicates user interaction and creates risks of error. It becomes
|
|
6
|
+
non-obvious what a user will activate with a click.
|
|
7
|
+
*/
|
|
8
|
+
exports.reporter = async (page, withItems) => await page.$$eval(
|
|
9
|
+
'a a, a button, a input, a select, button a, button button, button input, button select',
|
|
10
|
+
(bads, withItems) => {
|
|
11
|
+
// FUNCTION DEFINITION START
|
|
12
|
+
// Returns a space-minimized copy of a string.
|
|
13
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
14
|
+
// FUNCTION DEFINITION END
|
|
15
|
+
const totals = {
|
|
16
|
+
links: 0,
|
|
17
|
+
buttons: 0,
|
|
18
|
+
inputs: 0,
|
|
19
|
+
selects: 0
|
|
20
|
+
};
|
|
21
|
+
const items = [];
|
|
22
|
+
// Total and, if requested, itemize the faulty elements.
|
|
23
|
+
bads.forEach(bad => {
|
|
24
|
+
totals[Object.keys(totals)[['A', 'BUTTON', 'INPUT', 'SELECT'].indexOf(bad.tagName)]]++;
|
|
25
|
+
let container;
|
|
26
|
+
if (withItems) {
|
|
27
|
+
if (['A', 'BUTTON'].includes(bad.parentElement.tagName)) {
|
|
28
|
+
container = bad.parentElement;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
container = bad.parentElement.parentElement;
|
|
32
|
+
}
|
|
33
|
+
items.push({
|
|
34
|
+
embeddedElement: bad.tagName,
|
|
35
|
+
excerpt: compact(container.outerHTML)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Return the result.
|
|
40
|
+
const data = {totals};
|
|
41
|
+
const standardInstances = [];
|
|
42
|
+
const total = Object.values(data.totals).reduce((sum, current) => sum + current);
|
|
43
|
+
if (withItems) {
|
|
44
|
+
data.items = items;
|
|
45
|
+
items.forEach(item => {
|
|
46
|
+
standardInstances.push({
|
|
47
|
+
issueID: `embAc-${item.embeddedElement}`,
|
|
48
|
+
what: `Element ${item.embeddedElement} is embedded in a link or button`,
|
|
49
|
+
ordinalSeverity: 2,
|
|
50
|
+
location: {
|
|
51
|
+
doc: '',
|
|
52
|
+
type: '',
|
|
53
|
+
spec: ''
|
|
54
|
+
},
|
|
55
|
+
excerpt: item.excerpt
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (total) {
|
|
60
|
+
standardInstances.push({
|
|
61
|
+
issueID: 'embAc',
|
|
62
|
+
what: 'Interactive elements are contained by links or buttons',
|
|
63
|
+
ordinalSeverity: 2,
|
|
64
|
+
location: {
|
|
65
|
+
doc: '',
|
|
66
|
+
type: '',
|
|
67
|
+
spec: ''
|
|
68
|
+
},
|
|
69
|
+
excerpt: ''
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
data,
|
|
74
|
+
totals: [0, 0, total, 0],
|
|
75
|
+
standardInstances
|
|
76
|
+
};
|
|
77
|
+
}, withItems
|
|
78
|
+
);
|