testilo 42.3.1 → 43.0.1
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/AGENTS.md +21 -0
- package/README.md +6 -3
- package/package.json +4 -4
- package/procs/score/{tic49.js → tic.js} +860 -205
- package/procs/score/{tsp49.js → tsp.js} +5 -5
- package/procs/score/tic45.js +0 -9346
- package/procs/score/tsp45.js +0 -390
- package/procs/score/tsp46.js +0 -423
package/procs/score/tsp46.js
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
|
|
4
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
in the Software without restriction, including without limitation the rights
|
|
7
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
furnished to do so, subject to the following conditions:
|
|
10
|
-
|
|
11
|
-
The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
copies or substantial portions of the Software.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/*
|
|
24
|
-
tsp46
|
|
25
|
-
Testilo score proc 46
|
|
26
|
-
|
|
27
|
-
Computes target score data and adds them to a Testaro report.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
// IMPORTS
|
|
31
|
-
|
|
32
|
-
const {issues} = require('./tic45');
|
|
33
|
-
|
|
34
|
-
// MISCELLANEOUS CONSTANTS
|
|
35
|
-
|
|
36
|
-
// ID of this proc.
|
|
37
|
-
const scoreProcID = 'tsp46';
|
|
38
|
-
|
|
39
|
-
// WEIGHT CONSTANTS
|
|
40
|
-
// How much is added to the page score by each component.
|
|
41
|
-
|
|
42
|
-
// 1. Issue
|
|
43
|
-
// Each issue.
|
|
44
|
-
const issueCountWeight = 10;
|
|
45
|
-
/*
|
|
46
|
-
Expander of instance counts for issues with inherently limited instance counts. Divide this by
|
|
47
|
-
the maximum possible instance count and add the quotient to 1, then multiply the sum by the actual
|
|
48
|
-
instance count, i.e. the largest rule-quality-weighted instance count among the tools with any
|
|
49
|
-
instances of the issue.
|
|
50
|
-
*/
|
|
51
|
-
const maxWeight = 30;
|
|
52
|
-
|
|
53
|
-
// 2. Solo
|
|
54
|
-
|
|
55
|
-
// 3. Tool
|
|
56
|
-
/*
|
|
57
|
-
Severity: amount added to each raw tool score by each violation of a rule with ordinal severity 0
|
|
58
|
-
through 3.
|
|
59
|
-
*/
|
|
60
|
-
const severityWeights = [1, 2, 3, 4];
|
|
61
|
-
// Final: multiplier of the raw tool score to obtain the final tool score.
|
|
62
|
-
const toolWeight = 0.1;
|
|
63
|
-
|
|
64
|
-
// 4. Element
|
|
65
|
-
// Multiplier of the count of elements with at least 1 rule violation.
|
|
66
|
-
const elementWeight = 2;
|
|
67
|
-
|
|
68
|
-
// 5. Prevention
|
|
69
|
-
// Each tool prevention by the page.
|
|
70
|
-
const preventionWeight = 300;
|
|
71
|
-
// Each prevention of a Testaro rule test by the page.
|
|
72
|
-
const testaroRulePreventionWeight = 30;
|
|
73
|
-
|
|
74
|
-
// 6. Log
|
|
75
|
-
// Multipliers of log values to obtain the log score.
|
|
76
|
-
const logWeights = {
|
|
77
|
-
logCount: 0.1,
|
|
78
|
-
logSize: 0.002,
|
|
79
|
-
errorLogCount: 0.2,
|
|
80
|
-
errorLogSize: 0.004,
|
|
81
|
-
prohibitedCount: 3,
|
|
82
|
-
visitRejectionCount: 2
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// 7. Latency
|
|
86
|
-
// Normal latency (11 visits [1 per tool], with 2 seconds per visit).
|
|
87
|
-
const normalLatency = 22;
|
|
88
|
-
// Total latency exceeding normal, in seconds.
|
|
89
|
-
const latencyWeight = 2;
|
|
90
|
-
|
|
91
|
-
// RULE CONSTANTS
|
|
92
|
-
|
|
93
|
-
// Initialize a table of tool rules.
|
|
94
|
-
const issueIndex = {};
|
|
95
|
-
// Initialize an array of variably named tool rules.
|
|
96
|
-
const issueMatcher = [];
|
|
97
|
-
// For each issue:
|
|
98
|
-
Object.keys(issues).forEach(issueName => {
|
|
99
|
-
// For each tool with rules belonging to that issue:
|
|
100
|
-
Object.keys(issues[issueName].tools).forEach(toolName => {
|
|
101
|
-
// For each of those rules:
|
|
102
|
-
Object.keys(issues[issueName].tools[toolName]).forEach(ruleID => {
|
|
103
|
-
// Add it to the table of tool rules.
|
|
104
|
-
if (! issueIndex[toolName]) {
|
|
105
|
-
issueIndex[toolName] = {};
|
|
106
|
-
}
|
|
107
|
-
issueIndex[toolName][ruleID] = issueName;
|
|
108
|
-
// If it is variably named:
|
|
109
|
-
if (issues[issueName].tools[toolName][ruleID].variable) {
|
|
110
|
-
// Add it to the array of variably named tool rules.
|
|
111
|
-
issueMatcher.push(ruleID);
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// FUNCTIONS
|
|
118
|
-
|
|
119
|
-
// Scores a report.
|
|
120
|
-
exports.scorer = report => {
|
|
121
|
-
// If there are any acts in the report:
|
|
122
|
-
const {acts} = report;
|
|
123
|
-
if (Array.isArray(acts) && acts.length) {
|
|
124
|
-
// If any of them are test acts:
|
|
125
|
-
const testActs = acts.filter(act => act.type === 'test');
|
|
126
|
-
const testTools = new Set(testActs.map(act => act.which));
|
|
127
|
-
if (testActs.length) {
|
|
128
|
-
// Initialize the score data.
|
|
129
|
-
const score = {
|
|
130
|
-
scoreProcID,
|
|
131
|
-
weights: {
|
|
132
|
-
severities: severityWeights,
|
|
133
|
-
tool: toolWeight,
|
|
134
|
-
element: elementWeight,
|
|
135
|
-
log: logWeights,
|
|
136
|
-
latency: latencyWeight,
|
|
137
|
-
prevention: preventionWeight,
|
|
138
|
-
testaroRulePrevention: testaroRulePreventionWeight,
|
|
139
|
-
maxInstanceCount: maxWeight
|
|
140
|
-
},
|
|
141
|
-
normalLatency,
|
|
142
|
-
summary: {
|
|
143
|
-
total: 0,
|
|
144
|
-
issueCount: 0,
|
|
145
|
-
issue: 0,
|
|
146
|
-
solo: 0,
|
|
147
|
-
tool: 0,
|
|
148
|
-
element: 0,
|
|
149
|
-
prevention: 0,
|
|
150
|
-
log: 0,
|
|
151
|
-
latency: 0
|
|
152
|
-
},
|
|
153
|
-
details: {
|
|
154
|
-
severity: {
|
|
155
|
-
total: [0, 0, 0, 0],
|
|
156
|
-
byTool: {}
|
|
157
|
-
},
|
|
158
|
-
prevention: {},
|
|
159
|
-
issue: {},
|
|
160
|
-
solo: {},
|
|
161
|
-
tool: {},
|
|
162
|
-
element: {}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
// Initialize the global and issue-specific sets of path-identified elements.
|
|
166
|
-
const pathIDs = new Set();
|
|
167
|
-
const issuePaths = {};
|
|
168
|
-
const {summary, details} = score;
|
|
169
|
-
// For each test act:
|
|
170
|
-
testActs.forEach(act => {
|
|
171
|
-
const {data, which, standardResult} = act;
|
|
172
|
-
// If the tool is Testaro and the count of rule preventions was reported:
|
|
173
|
-
if (which === 'testaro' && data && data.rulePreventions) {
|
|
174
|
-
// Add their score to the score.
|
|
175
|
-
details.prevention.testaro = testaroRulePreventionWeight * data.rulePreventions.length;
|
|
176
|
-
}
|
|
177
|
-
// If the page prevented the tool from operating:
|
|
178
|
-
if (! standardResult || standardResult.prevented) {
|
|
179
|
-
// Add this to the score.
|
|
180
|
-
details.prevention[which] = preventionWeight;
|
|
181
|
-
}
|
|
182
|
-
// Otherwise, if a valid standard result exists:
|
|
183
|
-
else if (
|
|
184
|
-
standardResult
|
|
185
|
-
&& standardResult.totals
|
|
186
|
-
&& standardResult.totals.length === 4
|
|
187
|
-
&& standardResult.instances
|
|
188
|
-
) {
|
|
189
|
-
// Add the severity totals of the tool to the score.
|
|
190
|
-
const {totals} = standardResult;
|
|
191
|
-
details.severity.byTool[which] = totals;
|
|
192
|
-
// Add the severity-weighted tool totals to the score.
|
|
193
|
-
details.tool[which] = totals.reduce(
|
|
194
|
-
(sum, current, index) => sum + severityWeights[index] * current, 0
|
|
195
|
-
);
|
|
196
|
-
// For each instance of the tool:
|
|
197
|
-
standardResult.instances.forEach(instance => {
|
|
198
|
-
const {count, ordinalSeverity, pathID, ruleID, what} = instance;
|
|
199
|
-
count ??= 1;
|
|
200
|
-
// If the rule ID is not in the table of tool rules:
|
|
201
|
-
let canonicalRuleID = ruleID;
|
|
202
|
-
if (! issueIndex[which][ruleID]) {
|
|
203
|
-
// Convert it to the variably named tool rule that it matches, if any.
|
|
204
|
-
canonicalRuleID = issueMatcher.find(pattern => {
|
|
205
|
-
const patternRE = new RegExp(pattern);
|
|
206
|
-
return patternRE.test(ruleID);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
// If the rule has an ID:
|
|
210
|
-
if (canonicalRuleID) {
|
|
211
|
-
// Get the issue of the rule.
|
|
212
|
-
const issueName = issueIndex[which][canonicalRuleID];
|
|
213
|
-
// If the rule ID belongs to a non-ignorable issue:
|
|
214
|
-
if (issueName !== 'ignorable') {
|
|
215
|
-
// Add the instance to the issue details of the score data.
|
|
216
|
-
if (! details.issue[issueName]) {
|
|
217
|
-
details.issue[issueName] = {
|
|
218
|
-
summary: issues[issueName].summary,
|
|
219
|
-
wcag: issues[issueName].wcag || '',
|
|
220
|
-
score: 0,
|
|
221
|
-
maxCount: 0,
|
|
222
|
-
weight: issues[issueName].weight,
|
|
223
|
-
countLimit: issues[issueName].max,
|
|
224
|
-
instanceCounts: {},
|
|
225
|
-
tools: {}
|
|
226
|
-
};
|
|
227
|
-
if (! details.issue[issueName].countLimit) {
|
|
228
|
-
delete details.issue[issueName].countLimit;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (! details.issue[issueName].tools[which]) {
|
|
232
|
-
details.issue[issueName].tools[which] = {};
|
|
233
|
-
}
|
|
234
|
-
if (! details.issue[issueName].instanceCounts[which]) {
|
|
235
|
-
details.issue[issueName].instanceCounts[which] = 0;
|
|
236
|
-
}
|
|
237
|
-
details.issue[issueName].instanceCounts[which] += count;
|
|
238
|
-
if (! details.issue[issueName].tools[which][canonicalRuleID]) {
|
|
239
|
-
const ruleData = issues[issueName].tools[which][canonicalRuleID];
|
|
240
|
-
details.issue[issueName].tools[which][canonicalRuleID] = {
|
|
241
|
-
quality: ruleData.quality,
|
|
242
|
-
what: ruleData.what,
|
|
243
|
-
complaints: {
|
|
244
|
-
countTotal: 0,
|
|
245
|
-
texts: []
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
details
|
|
250
|
-
.issue[issueName]
|
|
251
|
-
.tools[which][canonicalRuleID]
|
|
252
|
-
.complaints
|
|
253
|
-
.countTotal += count || 1;
|
|
254
|
-
if (
|
|
255
|
-
! details
|
|
256
|
-
.issue[issueName]
|
|
257
|
-
.tools[which][canonicalRuleID]
|
|
258
|
-
.complaints
|
|
259
|
-
.texts
|
|
260
|
-
.includes(what)
|
|
261
|
-
) {
|
|
262
|
-
details
|
|
263
|
-
.issue[issueName]
|
|
264
|
-
.tools[which][canonicalRuleID]
|
|
265
|
-
.complaints
|
|
266
|
-
.texts
|
|
267
|
-
.push(what);
|
|
268
|
-
}
|
|
269
|
-
issuePaths[issueName] ??= new Set();
|
|
270
|
-
// If the element has a path ID:
|
|
271
|
-
if (pathID) {
|
|
272
|
-
// Ensure that it is in the issue-specific set of paths.
|
|
273
|
-
issuePaths[issueName].add(pathID);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// Otherwise, i.e. if the rule ID belongs to no issue:
|
|
278
|
-
else {
|
|
279
|
-
// Add the instance to the solo details of the score data.
|
|
280
|
-
if (! details.solo[which]) {
|
|
281
|
-
details.solo[which] = {};
|
|
282
|
-
}
|
|
283
|
-
if (! details.solo[which][ruleID]) {
|
|
284
|
-
details.solo[which][ruleID] = 0;
|
|
285
|
-
}
|
|
286
|
-
details.solo[which][ruleID] += (count || 1) * (ordinalSeverity + 1);
|
|
287
|
-
// Report this.
|
|
288
|
-
console.log(`ERROR: ${ruleID} of ${which} not found in issues`);
|
|
289
|
-
}
|
|
290
|
-
// Ensure that the element, if path-identified, is in the set of elements.
|
|
291
|
-
if (pathID) {
|
|
292
|
-
pathIDs.add(pathID);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
// Otherwise, i.e. if a failed standard result exists:
|
|
297
|
-
else {
|
|
298
|
-
// Add an inferred prevention to the score.
|
|
299
|
-
details.prevention[which] = preventionWeight;
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
// For each non-ignorable issue with any complaints:
|
|
303
|
-
Object.keys(details.issue).forEach(issueName => {
|
|
304
|
-
const issueData = details.issue[issueName];
|
|
305
|
-
// For each tool with any complaints for the issue:
|
|
306
|
-
Object.keys(issueData.tools).forEach(toolID => {
|
|
307
|
-
// Get the sum of the quality-weighted counts of its issue rules.
|
|
308
|
-
let weightedCount = 0;
|
|
309
|
-
Object.values(issueData.tools[toolID]).forEach(ruleData => {
|
|
310
|
-
weightedCount += ruleData.quality * ruleData.complaints.countTotal;
|
|
311
|
-
});
|
|
312
|
-
// If the sum exceeds the existing maximum weighted count for the issue:
|
|
313
|
-
if (weightedCount > issueData.maxCount) {
|
|
314
|
-
// Change the maximum count for the issue to the sum.
|
|
315
|
-
issueData.maxCount = weightedCount;
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
// Get the score for the issue, including any addition for the instance count limit.
|
|
319
|
-
const maxAddition = issueData.countLimit ? maxWeight / issueData.countLimit : 0;
|
|
320
|
-
issueData.score = Math.round(issueData.weight * issueData.maxCount * (1 + maxAddition));
|
|
321
|
-
// For each tool that has any rule of the issue:
|
|
322
|
-
Object.keys(issues[issueName].tools).forEach(toolName => {
|
|
323
|
-
// If the tool was in the job and has no instances of the issue:
|
|
324
|
-
if (testTools.has(toolName) && ! issueData.instanceCounts[toolName]) {
|
|
325
|
-
// Report its instance count as 0.
|
|
326
|
-
issueData.instanceCounts[toolName] = 0;
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
// Add the severity detail totals to the score.
|
|
331
|
-
details.severity.total = Object
|
|
332
|
-
.keys(details.severity.byTool)
|
|
333
|
-
.reduce((severityTotals, toolID) => {
|
|
334
|
-
details.severity.byTool[toolID].forEach((severityScore, index) => {
|
|
335
|
-
severityTotals[index] += severityScore;
|
|
336
|
-
});
|
|
337
|
-
return severityTotals;
|
|
338
|
-
}, details.severity.total);
|
|
339
|
-
// Add the element details to the score.
|
|
340
|
-
Object.keys(issuePaths).forEach(issueID => {
|
|
341
|
-
details.element[issueID] = Array.from(issuePaths[issueID]);
|
|
342
|
-
});
|
|
343
|
-
// Add the summary issue-count total to the score.
|
|
344
|
-
summary.issueCount = Object.keys(details.issue).length * issueCountWeight;
|
|
345
|
-
// Add the summary issue total to the score.
|
|
346
|
-
summary.issue = Object
|
|
347
|
-
.values(details.issue)
|
|
348
|
-
.reduce((total, current) => total + current.score, 0);
|
|
349
|
-
// Add the summary solo total to the score.
|
|
350
|
-
Object.keys(details.solo).forEach(tool => {
|
|
351
|
-
summary.solo += Object
|
|
352
|
-
.values(details.solo[tool])
|
|
353
|
-
.reduce((total, current) => total + current);
|
|
354
|
-
});
|
|
355
|
-
// Add the summary tool total to the score.
|
|
356
|
-
summary.tool = toolWeight * details.severity.total.reduce(
|
|
357
|
-
(total, current, index) => total + severityWeights[index] * current, 0
|
|
358
|
-
);
|
|
359
|
-
// Get the minimum count of violating elements.
|
|
360
|
-
const actRuleIDs = testActs.filter(act => act.standardResult).map(
|
|
361
|
-
act => act.standardResult.instances.map(instance => `${act.which}:${instance.ruleID}`)
|
|
362
|
-
);
|
|
363
|
-
const allRuleIDs = actRuleIDs.flat();
|
|
364
|
-
const ruleCounts = Array
|
|
365
|
-
.from(new Set(allRuleIDs))
|
|
366
|
-
.map(ruleID => allRuleIDs.filter(id => id === ruleID).length);
|
|
367
|
-
/*
|
|
368
|
-
Add the summary element total to the score, based on the count of identified violating
|
|
369
|
-
elements or the largest count of instances of violations of any rule, whichever is
|
|
370
|
-
greater.
|
|
371
|
-
*/
|
|
372
|
-
summary.element = elementWeight * Math.max(pathIDs.size, ... ruleCounts);
|
|
373
|
-
// Add the summary prevention total to the score.
|
|
374
|
-
summary.prevention = Object.values(details.prevention).reduce(
|
|
375
|
-
(total, current) => total + current, 0
|
|
376
|
-
);
|
|
377
|
-
// Add the summary log score to the score.
|
|
378
|
-
const {jobData} = report;
|
|
379
|
-
if (jobData) {
|
|
380
|
-
summary.log = Math.max(0, Math.round(
|
|
381
|
-
logWeights.logCount * jobData.logCount
|
|
382
|
-
+ logWeights.logSize * jobData.logSize +
|
|
383
|
-
+ logWeights.errorLogCount * jobData.errorLogCount
|
|
384
|
-
+ logWeights.errorLogSize * jobData.errorLogSize
|
|
385
|
-
+ logWeights.prohibitedCount * jobData.prohibitedCount +
|
|
386
|
-
+ logWeights.visitRejectionCount * jobData.visitRejectionCount
|
|
387
|
-
));
|
|
388
|
-
// Add the summary latency score to the score.
|
|
389
|
-
summary.latency = Math.round(
|
|
390
|
-
latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
// Round the unrounded scores.
|
|
394
|
-
Object.keys(summary).forEach(summaryTypeName => {
|
|
395
|
-
summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
|
|
396
|
-
});
|
|
397
|
-
details.severity.total.forEach((severityTotal, index) => {
|
|
398
|
-
details.severity.total[index] = Math.round(severityTotal);
|
|
399
|
-
});
|
|
400
|
-
// Add the summary total score to the score.
|
|
401
|
-
summary.total = summary.issueCount
|
|
402
|
-
+ summary.issue
|
|
403
|
-
+ summary.solo
|
|
404
|
-
+ summary.tool
|
|
405
|
-
+ summary.element
|
|
406
|
-
+ summary.prevention
|
|
407
|
-
+ summary.log
|
|
408
|
-
+ summary.latency;
|
|
409
|
-
// Add the score to the report or replace the existing score of the report.
|
|
410
|
-
report.score = score;
|
|
411
|
-
}
|
|
412
|
-
// Otherwise, i.e. if none of them is a test act:
|
|
413
|
-
else {
|
|
414
|
-
// Report this.
|
|
415
|
-
console.log('ERROR: No test acts');
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// Otherwise, i.e. if there are no acts in the report:
|
|
419
|
-
else {
|
|
420
|
-
// Report this.
|
|
421
|
-
console.log('ERROR: No acts');
|
|
422
|
-
}
|
|
423
|
-
};
|