testaro 14.6.2 → 14.7.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 +11 -11
- package/package.json +1 -1
- package/standardize.js +24 -18
- package/testaro/focInd.js +19 -19
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ As of this version, the counts of tests of the tools referenced above were:
|
|
|
65
65
|
- Testaro: 29
|
|
66
66
|
- total: 1356
|
|
67
67
|
|
|
68
|
-
Of the 29 Testaro tests, 26 are evaluative (they discover accessibility
|
|
68
|
+
Of the 29 Testaro tests, 26 are evaluative (they discover accessibility problems), and the other 3 (`elements`, `textNodes`, and `title`) are informative (they report conditions specified by the user).
|
|
69
69
|
|
|
70
70
|
## Quasi-tests
|
|
71
71
|
|
|
@@ -195,21 +195,21 @@ While each tool produces a _tool report_ of the results of its tests, Testaro al
|
|
|
195
195
|
|
|
196
196
|
##### Tool formats
|
|
197
197
|
|
|
198
|
-
The tools listed above as dependencies write their tool reports in various formats. They differ in how they organize multiple instances of the same
|
|
199
|
-
|
|
200
|
-
Integrating reports from 10 different tools is a complex task, as analyzed in [“Accessibility Metatesting”](https://arxiv.org/abs/2304.07591). The diversity of their reporting formats makes the task more complex than it would otherwise be.
|
|
198
|
+
The tools listed above as dependencies write their tool reports in various formats. They differ in how they organize multiple instances of the same problem, how they classify severity and certainty, how they point to the locations of problems, how they name problems, etc.
|
|
201
199
|
|
|
202
200
|
##### Standard format
|
|
203
201
|
|
|
204
202
|
Testaro helps overcome this format diversity by offering to represent the main facts in the report of each tool in a single standardized format.
|
|
205
203
|
|
|
204
|
+
In the conceptual scheme underlying the format standardization of Testaro, each tool has its own set of _rules_, where a rule is an algorithm for evaluating a target and determining whether instances of some kind of problem exist in it. With standardization, Testaro reports, in a uniform way, the outcomes from the application of rules by tools to a target.
|
|
205
|
+
|
|
206
206
|
If the `STANDARD` environment variable has the value `also` (which it has by default) or `only`, Testaro converts some data in each tool report to a standard format. That permits you to ignore the format idiosyncrasies of the tools. If `STANDARD` has the value `also`, the job report includes both formats. If the value is `only`, the job report includes only the standard format. If the value is `no`, the job report includes only the original format of each tool.
|
|
207
207
|
|
|
208
208
|
The standard format of each tool report has two properties:
|
|
209
|
-
- `totals`: an array of 4 integers, representing the counts of
|
|
209
|
+
- `totals`: an array of 4 integers, representing the counts of problem instances classified by the tool into 4 ordinal degrees of severity. For example, `[2, 13, 0, 5]` would mean that the tool reported 2 instances at the lowest severity, 13 at the next-lowest, none at the third-lowest, and 5 at the highest.
|
|
210
210
|
- `instances`: an array of objects describing facts about issue instances reported by the tool. This object has these properties, some of which have empty strings as values when the tool does not provide values:
|
|
211
|
-
- `
|
|
212
|
-
- `what`: a description of the
|
|
211
|
+
- `ruleID`: a code identifying a rule
|
|
212
|
+
- `what`: a description of the rule
|
|
213
213
|
- `count` (optional): the count of instances if this instance represents multiple instances
|
|
214
214
|
- `ordinalSeverity`: how the tool ranks the severity of the instance, on a 4-point ordinal scale
|
|
215
215
|
- `tagName`: upper-case tagName of the affected element
|
|
@@ -227,7 +227,7 @@ standardResult: {
|
|
|
227
227
|
totals: [2, 0, 17, 0],
|
|
228
228
|
instances: [
|
|
229
229
|
{
|
|
230
|
-
|
|
230
|
+
ruleID: 'rule01',
|
|
231
231
|
what: 'Button type invalid',
|
|
232
232
|
ordinalSeverity: 0,
|
|
233
233
|
tagName: 'BUTTON'
|
|
@@ -240,7 +240,7 @@ standardResult: {
|
|
|
240
240
|
excerpt: '<button type="link"></button>'
|
|
241
241
|
},
|
|
242
242
|
{
|
|
243
|
-
|
|
243
|
+
ruleID: 'rule01',
|
|
244
244
|
what: 'Button type invalid',
|
|
245
245
|
ordinalSeverity: 1,
|
|
246
246
|
tagName: 'BUTTON',
|
|
@@ -253,7 +253,7 @@ standardResult: {
|
|
|
253
253
|
excerpt: '<button type="important">Submit</button>'
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
|
-
|
|
256
|
+
ruleID: 'rule02',
|
|
257
257
|
what: 'Links have empty href attributes',
|
|
258
258
|
count: 17,
|
|
259
259
|
ordinalSeverity: 3,
|
|
@@ -272,7 +272,7 @@ standardResult: {
|
|
|
272
272
|
|
|
273
273
|
If a tool has the option to be used without itemization and is being so used, the `instances` array may be empty.
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
This standard format is not opinionated about issue classifications. A rule ID identifies something deemed to be an issue by a tool. Useful reporting from multi-tool testing still requires the classification of tool **rules** into **issues**. If tool `A` has `alt-incomplete` as a rule ID and tool `B` has `image_alt_stub` as a rule ID, Testaro does not decide whether those are really the same issue or different issues. That decision belongs to you. The standardization of tool reports by Testaro eliminates some of the drudgery in issue classification, but not any of the judgment required for issue classification.
|
|
276
276
|
|
|
277
277
|
### Acts
|
|
278
278
|
|
package/package.json
CHANGED
package/standardize.js
CHANGED
|
@@ -22,10 +22,15 @@ const cap = rawString => {
|
|
|
22
22
|
const getIdentifiers = code => {
|
|
23
23
|
let tagName = '';
|
|
24
24
|
let id = '';
|
|
25
|
+
// If the substring includes a tag of an element:
|
|
25
26
|
if (code && typeof code === 'string' && code.length && /<.+/s.test(code)) {
|
|
26
|
-
|
|
27
|
+
// Get the first start tag in the substring.
|
|
28
|
+
const startTag = code.replace(/^.*?<(?!\/)/s, '').replace(/>.*$/s, '').trim();
|
|
29
|
+
// If it exists:
|
|
27
30
|
if (startTag && startTag.length) {
|
|
28
|
-
|
|
31
|
+
// Get its tag name, upper-cased.
|
|
32
|
+
tagName = startTag.replace(/\s.*$/s, '').toUpperCase();
|
|
33
|
+
// Get the value of the id attribute of the start tag, if any.
|
|
29
34
|
const idArray = startTag.match(/\sid="([^"<>]+)"/);
|
|
30
35
|
if (idArray && idArray.length === 2) {
|
|
31
36
|
id = idArray[1];
|
|
@@ -53,7 +58,7 @@ const doAxe = (result, standardResult, certainty) => {
|
|
|
53
58
|
const ordinalSeverity = severityWeights[node.impact] + (certainty === 'violations' ? 2 : 0);
|
|
54
59
|
const identifiers = getIdentifiers(node.html);
|
|
55
60
|
const instance = {
|
|
56
|
-
|
|
61
|
+
ruleID: rule.id,
|
|
57
62
|
what: Array.from(whatSet.values()).join('; '),
|
|
58
63
|
ordinalSeverity,
|
|
59
64
|
tagName: identifiers[0],
|
|
@@ -79,7 +84,7 @@ const doHTMLCS = (result, standardResult, severity) => {
|
|
|
79
84
|
ruleData[what].forEach(item => {
|
|
80
85
|
const {tagName, id, code} = item;
|
|
81
86
|
const instance = {
|
|
82
|
-
|
|
87
|
+
ruleID,
|
|
83
88
|
what,
|
|
84
89
|
ordinalSeverity: ['Warning', '', '', 'Error'].indexOf(severity),
|
|
85
90
|
tagName: tagName.toUpperCase(),
|
|
@@ -113,7 +118,7 @@ const doNuVal = (result, standardResult, docType) => {
|
|
|
113
118
|
}
|
|
114
119
|
// Include the message twice, because in scoring it is likely to be replaced by a pattern.
|
|
115
120
|
const instance = {
|
|
116
|
-
|
|
121
|
+
ruleID: item.message,
|
|
117
122
|
what: item.message,
|
|
118
123
|
ordinalSeverity: -1,
|
|
119
124
|
tagName: identifiers[0],
|
|
@@ -154,14 +159,14 @@ const doQualWeb = (result, standardResult, ruleClassName) => {
|
|
|
154
159
|
failed: 3
|
|
155
160
|
}
|
|
156
161
|
};
|
|
157
|
-
Object.keys(ruleClass.assertions).forEach(
|
|
158
|
-
const ruleResult = ruleClass.assertions[
|
|
162
|
+
Object.keys(ruleClass.assertions).forEach(ruleID => {
|
|
163
|
+
const ruleResult = ruleClass.assertions[ruleID];
|
|
159
164
|
ruleResult.results.forEach(item => {
|
|
160
165
|
item.elements.forEach(element => {
|
|
161
166
|
const {htmlCode} = element;
|
|
162
167
|
const identifiers = getIdentifiers(htmlCode);
|
|
163
168
|
const instance = {
|
|
164
|
-
|
|
169
|
+
ruleID,
|
|
165
170
|
what: ruleResult.description,
|
|
166
171
|
ordinalSeverity: severities[ruleClassName][item.verdict],
|
|
167
172
|
tagName: identifiers[0],
|
|
@@ -185,8 +190,8 @@ const doWAVE = (result, standardResult, categoryName) => {
|
|
|
185
190
|
if (result.categories && result.categories[categoryName]) {
|
|
186
191
|
const category = result.categories[categoryName];
|
|
187
192
|
const ordinalSeverity = categoryName === 'alert' ? 0 : 3;
|
|
188
|
-
Object.keys(category.items).forEach(
|
|
189
|
-
category.items[
|
|
193
|
+
Object.keys(category.items).forEach(ruleID => {
|
|
194
|
+
category.items[ruleID].selectors.forEach(selector => {
|
|
190
195
|
let tagName = '';
|
|
191
196
|
let id = '';
|
|
192
197
|
if (typeof selector === 'string') {
|
|
@@ -201,8 +206,8 @@ const doWAVE = (result, standardResult, categoryName) => {
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
const instance = {
|
|
204
|
-
|
|
205
|
-
what: category.items[
|
|
209
|
+
ruleID,
|
|
210
|
+
what: category.items[ruleID].description,
|
|
206
211
|
ordinalSeverity,
|
|
207
212
|
tagName,
|
|
208
213
|
id,
|
|
@@ -234,16 +239,17 @@ const convert = (toolName, result, standardResult) => {
|
|
|
234
239
|
tagName = tagNameArray[1];
|
|
235
240
|
}
|
|
236
241
|
}
|
|
242
|
+
const {rule, target} = item;
|
|
237
243
|
const instance = {
|
|
238
|
-
|
|
239
|
-
what:
|
|
244
|
+
ruleID: rule.ruleID,
|
|
245
|
+
what: rule.ruleSummary,
|
|
240
246
|
ordinalSeverity: ['cantTell', '', '', 'failed'].indexOf(item.verdict),
|
|
241
247
|
tagName: tagName.toUpperCase() || identifiers[0],
|
|
242
248
|
id: identifiers[1],
|
|
243
249
|
location: {
|
|
244
250
|
doc: 'dom',
|
|
245
251
|
type: 'xpath',
|
|
246
|
-
spec:
|
|
252
|
+
spec: target.path
|
|
247
253
|
},
|
|
248
254
|
excerpt: cap(code)
|
|
249
255
|
};
|
|
@@ -281,7 +287,7 @@ const convert = (toolName, result, standardResult) => {
|
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
const instance = {
|
|
284
|
-
|
|
290
|
+
ruleID: item.engineTestId.toString(),
|
|
285
291
|
what: item.attributeDetail,
|
|
286
292
|
ordinalSeverity: 3,
|
|
287
293
|
tagName,
|
|
@@ -320,7 +326,7 @@ const convert = (toolName, result, standardResult) => {
|
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
328
|
const instance = {
|
|
323
|
-
|
|
329
|
+
ruleID: item.ruleId,
|
|
324
330
|
what: item.message,
|
|
325
331
|
ordinalSeverity: ['', 'recommendation', '', 'violation'].indexOf(item.level),
|
|
326
332
|
tagName: identifiers[0],
|
|
@@ -385,7 +391,7 @@ const convert = (toolName, result, standardResult) => {
|
|
|
385
391
|
}
|
|
386
392
|
}
|
|
387
393
|
const instance = {
|
|
388
|
-
|
|
394
|
+
ruleID: item.tID ? item.tID.toString() : '',
|
|
389
395
|
what: item.errorTitle || '',
|
|
390
396
|
ordinalSeverity: Math.min(
|
|
391
397
|
3, Math.max(0, Math.round((item.certainty || 0) * (item.priority || 0) / 3333))
|
package/testaro/focInd.js
CHANGED
|
@@ -27,15 +27,15 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
27
27
|
totals: {
|
|
28
28
|
total: 0,
|
|
29
29
|
types: {
|
|
30
|
-
|
|
30
|
+
missing: {
|
|
31
31
|
total: 0,
|
|
32
32
|
tagNames: {}
|
|
33
33
|
},
|
|
34
|
-
|
|
34
|
+
nonoutline: {
|
|
35
35
|
total: 0,
|
|
36
36
|
tagNames: {}
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
outline: {
|
|
39
39
|
total: 0,
|
|
40
40
|
meanDelay: 0,
|
|
41
41
|
tagNames: {}
|
|
@@ -45,16 +45,16 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
45
45
|
};
|
|
46
46
|
if (withItems) {
|
|
47
47
|
data.items = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
missing: [],
|
|
49
|
+
nonoutline: [],
|
|
50
|
+
outline: []
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
// Adds facts about an element to the result.
|
|
54
54
|
const addElementFacts = (element, status, delay = null) => {
|
|
55
55
|
const type = data.totals.types[status];
|
|
56
56
|
type.total++;
|
|
57
|
-
if (status === '
|
|
57
|
+
if (status === 'outline') {
|
|
58
58
|
type.meanDelay = Math.round(((type.total - 1) * type.meanDelay + delay) / type.total);
|
|
59
59
|
}
|
|
60
60
|
const tagName = element.tagName;
|
|
@@ -71,7 +71,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
71
71
|
text: (element.textContent.trim() || element.outerHTML.trim()).replace(/\s+/g, ' ')
|
|
72
72
|
.slice(0, 100)
|
|
73
73
|
};
|
|
74
|
-
if (status === '
|
|
74
|
+
if (status === 'outline') {
|
|
75
75
|
elementData.delay = delay;
|
|
76
76
|
}
|
|
77
77
|
data.items[status].push(elementData);
|
|
@@ -111,7 +111,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
111
111
|
// If a non-transparent outline appeared immediately on focus:
|
|
112
112
|
if (styleDec.outlineWidth !== '0px' && styleDec.outlineColor !== 'rgba(0, 0, 0, 0)') {
|
|
113
113
|
// Add facts about the element to the result.
|
|
114
|
-
addElementFacts(element, '
|
|
114
|
+
addElementFacts(element, 'outline', 0);
|
|
115
115
|
hasOutline = true;
|
|
116
116
|
}
|
|
117
117
|
// Otherwise, if a wait for an outline is allowed:
|
|
@@ -137,7 +137,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
137
137
|
const delay = await outlineDelay;
|
|
138
138
|
if (delay) {
|
|
139
139
|
// Add facts about the element to the result.
|
|
140
|
-
addElementFacts(element, '
|
|
140
|
+
addElementFacts(element, 'outline', delay);
|
|
141
141
|
hasOutline = true;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -158,7 +158,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
158
158
|
|| hasDiffBorder
|
|
159
159
|
|| indicatorStyleNames.slice(5).reduce((any, styleName) => any || diff(styleName), false);
|
|
160
160
|
// Add the determination to the result.
|
|
161
|
-
const status = hasIndicator ? '
|
|
161
|
+
const status = hasIndicator ? 'nonoutline' : 'missing';
|
|
162
162
|
addElementFacts(element, status);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -166,17 +166,17 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
166
166
|
return data;
|
|
167
167
|
}, [allowedDelay, withItems]);
|
|
168
168
|
const {types} = data.totals;
|
|
169
|
-
const totals = [0, 0, types.
|
|
169
|
+
const totals = [0, 0, types.nonoutline.total, types.missing.total];
|
|
170
170
|
const standardInstances = [];
|
|
171
171
|
if (data.items) {
|
|
172
|
-
const issueNames = ['
|
|
172
|
+
const issueNames = ['nonoutline', 'missing'];
|
|
173
173
|
issueNames.forEach(issueName => {
|
|
174
174
|
data.items[issueName].forEach(item => {
|
|
175
|
-
const qualifier = issueName === '
|
|
175
|
+
const qualifier = issueName === 'nonoutline' ? 'a non-outline' : 'no';
|
|
176
176
|
standardInstances.push({
|
|
177
177
|
issueID: `focInd-${issueName}`,
|
|
178
178
|
what: `${item.tagName} element has ${qualifier} focus indicator`,
|
|
179
|
-
ordinalSeverity: issueName === '
|
|
179
|
+
ordinalSeverity: issueName === 'nonoutline' ? 2 : 3,
|
|
180
180
|
tagName: item.tagName,
|
|
181
181
|
id: item.id,
|
|
182
182
|
location: {
|
|
@@ -190,11 +190,11 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
else {
|
|
193
|
-
if (types.
|
|
193
|
+
if (types.missing.total) {
|
|
194
194
|
standardInstances.push({
|
|
195
195
|
issueID: 'focInd-missing',
|
|
196
196
|
what: 'Elements have missing focus indicators',
|
|
197
|
-
count: types.
|
|
197
|
+
count: types.missing.total,
|
|
198
198
|
ordinalSeverity: 3,
|
|
199
199
|
tagName: '',
|
|
200
200
|
id: '',
|
|
@@ -206,11 +206,11 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
|
|
|
206
206
|
excerpt: ''
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
|
-
if (types.
|
|
209
|
+
if (types.nonoutline.total) {
|
|
210
210
|
standardInstances.push({
|
|
211
211
|
issueID: 'focInd-nonoutline',
|
|
212
212
|
what: 'Elements have non-outline focus indicators',
|
|
213
|
-
count: types.
|
|
213
|
+
count: types.nonoutline.total,
|
|
214
214
|
ordinalSeverity: 2,
|
|
215
215
|
tagName: '',
|
|
216
216
|
id: '',
|