testilo 21.2.3 → 21.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 +1 -1
- package/procs/score/tic38.js +5 -5
- package/procs/score/tsp38-old.js +300 -0
- package/procs/score/tsp38.js +52 -44
package/package.json
CHANGED
package/procs/score/tic38.js
CHANGED
|
@@ -1350,17 +1350,17 @@ exports.issues = {
|
|
|
1350
1350
|
}
|
|
1351
1351
|
}
|
|
1352
1352
|
},
|
|
1353
|
-
|
|
1354
|
-
summary: 'label contains
|
|
1353
|
+
controlIDInLabelBad: {
|
|
1354
|
+
summary: 'label contains control with nonmatching ID',
|
|
1355
1355
|
why: 'User cannot get help understanding an item in a form',
|
|
1356
1356
|
wcag: '1.3.1',
|
|
1357
1357
|
weight: 4,
|
|
1358
1358
|
tools: {
|
|
1359
1359
|
nuVal: {
|
|
1360
|
-
'Any
|
|
1361
|
-
variable:
|
|
1360
|
+
'^Any .+ descendant of a label element with a for attribute must have an ID value that matches that for attribute.*$': {
|
|
1361
|
+
variable: true,
|
|
1362
1362
|
quality: 1,
|
|
1363
|
-
what: 'label element has a
|
|
1363
|
+
what: 'label element has a labelable descendant whose ID differs from the for attribute of the label'
|
|
1364
1364
|
}
|
|
1365
1365
|
}
|
|
1366
1366
|
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/*
|
|
2
|
+
tsp37
|
|
3
|
+
Testilo score proc 37
|
|
4
|
+
|
|
5
|
+
Computes target score data and adds them to a ts37 report.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// IMPORTS
|
|
9
|
+
|
|
10
|
+
const {issues} = require('./tic38');
|
|
11
|
+
|
|
12
|
+
// CONSTANTS
|
|
13
|
+
|
|
14
|
+
// ID of this proc.
|
|
15
|
+
const scoreProcID = 'tsp38';
|
|
16
|
+
// Latency weight (how much each second of excess latency adds to the score).
|
|
17
|
+
const latencyWeight = 1;
|
|
18
|
+
// Normal latency (6 visits, with 1.5 second per visit).
|
|
19
|
+
const normalLatency = 9;
|
|
20
|
+
// Prevention weight (how much each prevention adds to the score).
|
|
21
|
+
const preventionWeight = 300;
|
|
22
|
+
// Maximum instance count addition weight (divisor of max).
|
|
23
|
+
const maxWeight = 30;
|
|
24
|
+
// Other weights.
|
|
25
|
+
const severityWeights = [1, 2, 3, 4];
|
|
26
|
+
const toolWeight = 0.1;
|
|
27
|
+
const logWeights = {
|
|
28
|
+
logCount: 0.1,
|
|
29
|
+
logSize: 0.002,
|
|
30
|
+
errorLogCount: 0.2,
|
|
31
|
+
errorLogSize: 0.004,
|
|
32
|
+
prohibitedCount: 3,
|
|
33
|
+
visitRejectionCount: 2
|
|
34
|
+
};
|
|
35
|
+
// Initialize a table of tool rules.
|
|
36
|
+
const issueIndex = {};
|
|
37
|
+
// Initialize an array of variably named tool rules.
|
|
38
|
+
const issueMatcher = [];
|
|
39
|
+
// For each issue:
|
|
40
|
+
Object.keys(issues).forEach(issueName => {
|
|
41
|
+
// For each tool with rules belonging to that issue:
|
|
42
|
+
Object.keys(issues[issueName].tools).forEach(toolName => {
|
|
43
|
+
// For each of those rules:
|
|
44
|
+
Object.keys(issues[issueName].tools[toolName]).forEach(ruleID => {
|
|
45
|
+
// Add it to the table of tool rules.
|
|
46
|
+
if (! issueIndex[toolName]) {
|
|
47
|
+
issueIndex[toolName] = {};
|
|
48
|
+
}
|
|
49
|
+
issueIndex[toolName][ruleID] = issueName;
|
|
50
|
+
// If it is variably named:
|
|
51
|
+
if (issues[issueName].tools[toolName][ruleID].variable) {
|
|
52
|
+
// Add it to the array of variably named tool rules.
|
|
53
|
+
issueMatcher.push(ruleID);
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// FUNCTIONS
|
|
60
|
+
|
|
61
|
+
// Scores a report.
|
|
62
|
+
exports.scorer = report => {
|
|
63
|
+
// If there are any acts in the report:
|
|
64
|
+
const {acts} = report;
|
|
65
|
+
if (Array.isArray(acts) && acts.length) {
|
|
66
|
+
// If any of them are test acts:
|
|
67
|
+
const testActs = acts.filter(act => act.type === 'test');
|
|
68
|
+
if (testActs.length) {
|
|
69
|
+
// Initialize the score data.
|
|
70
|
+
const score = {
|
|
71
|
+
scoreProcID,
|
|
72
|
+
weights: {
|
|
73
|
+
severities: severityWeights,
|
|
74
|
+
tool: toolWeight,
|
|
75
|
+
log: logWeights,
|
|
76
|
+
latency: latencyWeight,
|
|
77
|
+
prevention: preventionWeight,
|
|
78
|
+
maxInstanceCount: maxWeight
|
|
79
|
+
},
|
|
80
|
+
normalLatency,
|
|
81
|
+
summary: {
|
|
82
|
+
total: 0,
|
|
83
|
+
issue: 0,
|
|
84
|
+
solo: 0,
|
|
85
|
+
tool: 0,
|
|
86
|
+
prevention: 0,
|
|
87
|
+
log: 0,
|
|
88
|
+
latency: 0
|
|
89
|
+
},
|
|
90
|
+
details: {
|
|
91
|
+
severity: {
|
|
92
|
+
total: [0, 0, 0, 0],
|
|
93
|
+
byTool: {}
|
|
94
|
+
},
|
|
95
|
+
prevention: {},
|
|
96
|
+
issue: {},
|
|
97
|
+
solo: {},
|
|
98
|
+
tool: {}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const {summary, details} = score;
|
|
102
|
+
// For each test act:
|
|
103
|
+
testActs.forEach(act => {
|
|
104
|
+
// If the page prevented the tool from operating:
|
|
105
|
+
const {which, standardResult} = act;
|
|
106
|
+
if (! standardResult || standardResult.prevented) {
|
|
107
|
+
// Add this to the score.
|
|
108
|
+
details.prevention[which] = preventionWeight;
|
|
109
|
+
}
|
|
110
|
+
// Otherwise, if a valid standard result exists:
|
|
111
|
+
else if (
|
|
112
|
+
standardResult
|
|
113
|
+
&& standardResult.totals
|
|
114
|
+
&& standardResult.totals.length === 4
|
|
115
|
+
&& standardResult.instances
|
|
116
|
+
) {
|
|
117
|
+
// Add the severity totals of the tool to the score.
|
|
118
|
+
const {totals} = standardResult;
|
|
119
|
+
details.severity.byTool[which] = totals;
|
|
120
|
+
// Add the severity-weighted tool totals to the score.
|
|
121
|
+
details.tool[which] = totals.reduce(
|
|
122
|
+
(sum, current, index) => sum + severityWeights[index] * current, 0
|
|
123
|
+
);
|
|
124
|
+
// For each instance of the tool:
|
|
125
|
+
standardResult.instances.forEach(instance => {
|
|
126
|
+
// Get the rule ID.
|
|
127
|
+
const {ruleID, ordinalSeverity, count} = instance;
|
|
128
|
+
// If it is not in the table of tool rules:
|
|
129
|
+
let canonicalRuleID = ruleID;
|
|
130
|
+
if (! issueIndex[which][ruleID]) {
|
|
131
|
+
// Convert it to the variably named tool rule that it matches, if any.
|
|
132
|
+
canonicalRuleID = issueMatcher.find(pattern => {
|
|
133
|
+
const patternRE = new RegExp(pattern);
|
|
134
|
+
return patternRE.test(instance.ruleID);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// If the rule ID belongs to an issue:
|
|
138
|
+
if (canonicalRuleID) {
|
|
139
|
+
// Get the issue.
|
|
140
|
+
const issueName = issueIndex[which][canonicalRuleID];
|
|
141
|
+
// Add the instance to the issue details of the score data.
|
|
142
|
+
if (! details.issue[issueName]) {
|
|
143
|
+
details.issue[issueName] = {
|
|
144
|
+
summary: issues[issueName].summary,
|
|
145
|
+
score: 0,
|
|
146
|
+
maxCount: 0,
|
|
147
|
+
weight: issues[issueName].weight,
|
|
148
|
+
countLimit: issues[issueName].max,
|
|
149
|
+
tools: {}
|
|
150
|
+
};
|
|
151
|
+
if (! details.issue[issueName].countLimit) {
|
|
152
|
+
delete details.issue[issueName].countLimit;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (! details.issue[issueName].tools[which]) {
|
|
156
|
+
details.issue[issueName].tools[which] = {};
|
|
157
|
+
}
|
|
158
|
+
if (! details.issue[issueName].tools[which][canonicalRuleID]) {
|
|
159
|
+
const ruleData = issues[issueName].tools[which][canonicalRuleID];
|
|
160
|
+
details.issue[issueName].tools[which][canonicalRuleID] = {
|
|
161
|
+
quality: ruleData.quality,
|
|
162
|
+
what: ruleData.what,
|
|
163
|
+
complaints: {
|
|
164
|
+
countTotal: 0,
|
|
165
|
+
texts: []
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
details
|
|
170
|
+
.issue[issueName]
|
|
171
|
+
.tools[which][canonicalRuleID]
|
|
172
|
+
.complaints
|
|
173
|
+
.countTotal += instance.count || 1;
|
|
174
|
+
if (
|
|
175
|
+
! details
|
|
176
|
+
.issue[issueName]
|
|
177
|
+
.tools[which][canonicalRuleID]
|
|
178
|
+
.complaints
|
|
179
|
+
.texts
|
|
180
|
+
.includes(instance.what)
|
|
181
|
+
) {
|
|
182
|
+
details
|
|
183
|
+
.issue[issueName]
|
|
184
|
+
.tools[which][canonicalRuleID]
|
|
185
|
+
.complaints
|
|
186
|
+
.texts
|
|
187
|
+
.push(instance.what);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Otherwise, i.e. if the rule ID belongs to no issue:
|
|
191
|
+
else {
|
|
192
|
+
// Add the instance to the solo details of the score data.
|
|
193
|
+
if (! details.solo[which]) {
|
|
194
|
+
details.solo[which] = {};
|
|
195
|
+
}
|
|
196
|
+
if (! details.solo[which][ruleID]) {
|
|
197
|
+
details.solo[which][ruleID] = 0;
|
|
198
|
+
}
|
|
199
|
+
details.solo[which][ruleID] += (count || 1) * (ordinalSeverity + 1);
|
|
200
|
+
// Report this.
|
|
201
|
+
console.log(`ERROR: ${instance.ruleID} of ${which} not found in issues`);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Otherwise, i.e. if a failed standard result exists:
|
|
206
|
+
else {
|
|
207
|
+
// Add an inferred prevention to the score.
|
|
208
|
+
details.prevention[which] = preventionWeight;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// For each issue with any complaints:
|
|
212
|
+
Object.keys(details.issue).forEach(issueName => {
|
|
213
|
+
const issueData = details.issue[issueName];
|
|
214
|
+
// For each tool with any complaints for the issue:
|
|
215
|
+
Object.keys(issueData.tools).forEach(toolID => {
|
|
216
|
+
// Get the sum of the quality-weighted counts of its issue rules.
|
|
217
|
+
let weightedCount = 0;
|
|
218
|
+
Object.values(issueData.tools[toolID]).forEach(ruleData => {
|
|
219
|
+
weightedCount += ruleData.quality * ruleData.complaints.countTotal;
|
|
220
|
+
});
|
|
221
|
+
// If the sum exceeds the existing maximum weighted count for the issue:
|
|
222
|
+
if (weightedCount > issueData.maxCount) {
|
|
223
|
+
// Change the maximum count for the issue to the sum.
|
|
224
|
+
issueData.maxCount = weightedCount;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// Get the score for the issue, including any addition for the instance count limit.
|
|
228
|
+
const maxAddition = issueData.countLimit ? maxWeight / issueData.countLimit : 0;
|
|
229
|
+
issueData.score = Math.round(issueData.weight * issueData.maxCount * (1 + maxAddition));
|
|
230
|
+
});
|
|
231
|
+
// Add the severity detail totals to the score.
|
|
232
|
+
details.severity.total = Object.keys(details.severity.byTool).reduce((severityTotals, toolID) => {
|
|
233
|
+
details.severity.byTool[toolID].forEach((severityScore, index) => {
|
|
234
|
+
severityTotals[index] += severityScore;
|
|
235
|
+
});
|
|
236
|
+
return severityTotals;
|
|
237
|
+
}, details.severity.total);
|
|
238
|
+
// Add the summary issue total to the score.
|
|
239
|
+
summary.issue = Object
|
|
240
|
+
.values(details.issue)
|
|
241
|
+
.reduce((total, current) => total + current.score, 0);
|
|
242
|
+
// Add the summary solo total to the score.
|
|
243
|
+
Object.keys(details.solo).forEach(tool => {
|
|
244
|
+
summary.solo += Object
|
|
245
|
+
.values(details.solo[tool])
|
|
246
|
+
.reduce((total, current) => total + current);
|
|
247
|
+
});
|
|
248
|
+
// Add the summary tool total to the score.
|
|
249
|
+
summary.tool = toolWeight * details.severity.total.reduce(
|
|
250
|
+
(total, current, index) => total + severityWeights[index] * current, 0
|
|
251
|
+
);
|
|
252
|
+
// Add the summary prevention total to the score.
|
|
253
|
+
summary.prevention = Object.values(details.prevention).reduce(
|
|
254
|
+
(total, current) => total + current, 0
|
|
255
|
+
);
|
|
256
|
+
// Add the summary log score to the score.
|
|
257
|
+
const {jobData} = report;
|
|
258
|
+
if (jobData) {
|
|
259
|
+
summary.log = Math.max(0, Math.round(
|
|
260
|
+
logWeights.logCount * jobData.logCount
|
|
261
|
+
+ logWeights.logSize * jobData.logSize +
|
|
262
|
+
+ logWeights.errorLogCount * jobData.errorLogCount
|
|
263
|
+
+ logWeights.errorLogSize * jobData.errorLogSize
|
|
264
|
+
+ logWeights.prohibitedCount * jobData.prohibitedCount +
|
|
265
|
+
+ logWeights.visitRejectionCount * jobData.visitRejectionCount
|
|
266
|
+
));
|
|
267
|
+
// Add the summary latency score to the score.
|
|
268
|
+
summary.latency = Math.round(
|
|
269
|
+
latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
// Round the unrounded scores.
|
|
273
|
+
Object.keys(summary).forEach(summaryTypeName => {
|
|
274
|
+
summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
|
|
275
|
+
});
|
|
276
|
+
details.severity.total.forEach((severityTotal, index) => {
|
|
277
|
+
details.severity.total[index] = Math.round(severityTotal);
|
|
278
|
+
});
|
|
279
|
+
// Add the summary total score to the score.
|
|
280
|
+
summary.total = summary.issue
|
|
281
|
+
+ summary.solo
|
|
282
|
+
+ summary.tool
|
|
283
|
+
+ summary.prevention
|
|
284
|
+
+ summary.log
|
|
285
|
+
+ summary.latency;
|
|
286
|
+
// Add the score to the report or replace the existing score of the report.
|
|
287
|
+
report.score = score;
|
|
288
|
+
}
|
|
289
|
+
// Otherwise, i.e. if none of them is a test act:
|
|
290
|
+
else {
|
|
291
|
+
// Report this.
|
|
292
|
+
console.log('ERROR: No test acts');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Otherwise, i.e. if there are no acts in the report:
|
|
296
|
+
else {
|
|
297
|
+
// Report this.
|
|
298
|
+
console.log('ERROR: No acts');
|
|
299
|
+
}
|
|
300
|
+
};
|
package/procs/score/tsp38.js
CHANGED
|
@@ -21,6 +21,8 @@ const normalLatency = 9;
|
|
|
21
21
|
const preventionWeight = 300;
|
|
22
22
|
// Maximum instance count addition weight (divisor of max).
|
|
23
23
|
const maxWeight = 30;
|
|
24
|
+
// Issue count weight.
|
|
25
|
+
const issueCountWeight = 15;
|
|
24
26
|
// Other weights.
|
|
25
27
|
const severityWeights = [1, 2, 3, 4];
|
|
26
28
|
const toolWeight = 0.1;
|
|
@@ -80,6 +82,7 @@ exports.scorer = report => {
|
|
|
80
82
|
normalLatency,
|
|
81
83
|
summary: {
|
|
82
84
|
total: 0,
|
|
85
|
+
issueCount: 0,
|
|
83
86
|
issue: 0,
|
|
84
87
|
solo: 0,
|
|
85
88
|
tool: 0,
|
|
@@ -134,57 +137,59 @@ exports.scorer = report => {
|
|
|
134
137
|
return patternRE.test(instance.ruleID);
|
|
135
138
|
});
|
|
136
139
|
}
|
|
137
|
-
// If the rule ID belongs to
|
|
140
|
+
// If the rule ID belongs to a non-ignorable issue:
|
|
138
141
|
if (canonicalRuleID) {
|
|
139
142
|
// Get the issue.
|
|
140
143
|
const issueName = issueIndex[which][canonicalRuleID];
|
|
141
144
|
// Add the instance to the issue details of the score data.
|
|
142
|
-
if (
|
|
143
|
-
details.issue[issueName]
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
if (! details.issue[issueName].tools[which]) {
|
|
156
|
-
details.issue[issueName].tools[which] = {};
|
|
157
|
-
}
|
|
158
|
-
if (! details.issue[issueName].tools[which][canonicalRuleID]) {
|
|
159
|
-
const ruleData = issues[issueName].tools[which][canonicalRuleID];
|
|
160
|
-
details.issue[issueName].tools[which][canonicalRuleID] = {
|
|
161
|
-
quality: ruleData.quality,
|
|
162
|
-
what: ruleData.what,
|
|
163
|
-
complaints: {
|
|
164
|
-
countTotal: 0,
|
|
165
|
-
texts: []
|
|
145
|
+
if (issueName !== 'ignorable') {
|
|
146
|
+
if (! details.issue[issueName]) {
|
|
147
|
+
details.issue[issueName] = {
|
|
148
|
+
summary: issues[issueName].summary,
|
|
149
|
+
score: 0,
|
|
150
|
+
maxCount: 0,
|
|
151
|
+
weight: issues[issueName].weight,
|
|
152
|
+
countLimit: issues[issueName].max,
|
|
153
|
+
tools: {}
|
|
154
|
+
};
|
|
155
|
+
if (! details.issue[issueName].countLimit) {
|
|
156
|
+
delete details.issue[issueName].countLimit;
|
|
166
157
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
158
|
+
}
|
|
159
|
+
if (! details.issue[issueName].tools[which]) {
|
|
160
|
+
details.issue[issueName].tools[which] = {};
|
|
161
|
+
}
|
|
162
|
+
if (! details.issue[issueName].tools[which][canonicalRuleID]) {
|
|
163
|
+
const ruleData = issues[issueName].tools[which][canonicalRuleID];
|
|
164
|
+
details.issue[issueName].tools[which][canonicalRuleID] = {
|
|
165
|
+
quality: ruleData.quality,
|
|
166
|
+
what: ruleData.what,
|
|
167
|
+
complaints: {
|
|
168
|
+
countTotal: 0,
|
|
169
|
+
texts: []
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
182
173
|
details
|
|
183
174
|
.issue[issueName]
|
|
184
175
|
.tools[which][canonicalRuleID]
|
|
185
176
|
.complaints
|
|
186
|
-
.
|
|
187
|
-
|
|
177
|
+
.countTotal += instance.count || 1;
|
|
178
|
+
if (
|
|
179
|
+
! details
|
|
180
|
+
.issue[issueName]
|
|
181
|
+
.tools[which][canonicalRuleID]
|
|
182
|
+
.complaints
|
|
183
|
+
.texts
|
|
184
|
+
.includes(instance.what)
|
|
185
|
+
) {
|
|
186
|
+
details
|
|
187
|
+
.issue[issueName]
|
|
188
|
+
.tools[which][canonicalRuleID]
|
|
189
|
+
.complaints
|
|
190
|
+
.texts
|
|
191
|
+
.push(instance.what);
|
|
192
|
+
}
|
|
188
193
|
}
|
|
189
194
|
}
|
|
190
195
|
// Otherwise, i.e. if the rule ID belongs to no issue:
|
|
@@ -208,7 +213,7 @@ exports.scorer = report => {
|
|
|
208
213
|
details.prevention[which] = preventionWeight;
|
|
209
214
|
}
|
|
210
215
|
});
|
|
211
|
-
// For each issue with any complaints:
|
|
216
|
+
// For each non-ignorable issue with any complaints:
|
|
212
217
|
Object.keys(details.issue).forEach(issueName => {
|
|
213
218
|
const issueData = details.issue[issueName];
|
|
214
219
|
// For each tool with any complaints for the issue:
|
|
@@ -235,6 +240,8 @@ exports.scorer = report => {
|
|
|
235
240
|
});
|
|
236
241
|
return severityTotals;
|
|
237
242
|
}, details.severity.total);
|
|
243
|
+
// Add the summary issue-count total to the score.
|
|
244
|
+
summary.issueCount = Object.keys(details.issue).length * issueCountWeight;
|
|
238
245
|
// Add the summary issue total to the score.
|
|
239
246
|
summary.issue = Object
|
|
240
247
|
.values(details.issue)
|
|
@@ -277,7 +284,8 @@ exports.scorer = report => {
|
|
|
277
284
|
details.severity.total[index] = Math.round(severityTotal);
|
|
278
285
|
});
|
|
279
286
|
// Add the summary total score to the score.
|
|
280
|
-
summary.total = summary.
|
|
287
|
+
summary.total = summary.issueCount
|
|
288
|
+
+ summary.issue
|
|
281
289
|
+ summary.solo
|
|
282
290
|
+ summary.tool
|
|
283
291
|
+ summary.prevention
|