testilo 12.3.1 → 13.2.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 +1 -2
- package/call.js +6 -6
- package/digest.js +4 -12
- package/package.json +1 -1
- package/procs/digest/tdp27/index.html +24 -12
- package/procs/digest/tdp27/index.js +46 -34
- package/procs/score/tic27.js +9 -106
- package/procs/score/tsp27.js +106 -50
package/README.md
CHANGED
|
@@ -181,8 +181,7 @@ The `batch()` function of the `batch` module generates a batch and returns it as
|
|
|
181
181
|
A user can invoke `batch` in this way:
|
|
182
182
|
|
|
183
183
|
- Create a target list and save it as a text file (with tab-delimited items in newline-delimited lines) in the `targetLists` subdirectory of the `process.env.SPECDIR` directory. Name the file `x.tsv`, where `x` is the list ID.
|
|
184
|
-
- In the Testilo project directory, execute
|
|
185
|
-
- `node call batch i w`
|
|
184
|
+
- In the Testilo project directory, execute the statement `node call batch i w`.
|
|
186
185
|
|
|
187
186
|
In this statement, replace `i` with the list ID and `w` with a description of the batch.
|
|
188
187
|
|
package/call.js
CHANGED
|
@@ -58,7 +58,7 @@ const callBatch = async (listID, what) => {
|
|
|
58
58
|
const callScript = async (scriptID, classificationID = null, ... issueIDs) => {
|
|
59
59
|
// Get any issue classification.
|
|
60
60
|
const issueClasses = classificationID
|
|
61
|
-
? require(`${functionDir}/score/${classificationID}`)
|
|
61
|
+
? require(`${functionDir}/score/${classificationID}`).issueClasses
|
|
62
62
|
: null;
|
|
63
63
|
// Create a script.
|
|
64
64
|
const scriptObj = script(scriptID, issueClasses, ... issueIDs);
|
|
@@ -117,7 +117,8 @@ const callScore = async (scorerID, selector = '') => {
|
|
|
117
117
|
// Save it.
|
|
118
118
|
await fs.writeFile(`${scoredReportDir}/${report.id}.json`, JSON.stringify(report, null, 2));
|
|
119
119
|
};
|
|
120
|
-
|
|
120
|
+
console.log(`Reports scored and saved in ${scoredReportDir}`);
|
|
121
|
+
}
|
|
121
122
|
// Otherwise, i.e. if no raw reports are to be scored:
|
|
122
123
|
else {
|
|
123
124
|
// Report this.
|
|
@@ -132,17 +133,16 @@ const callDigest = async (digesterID, selector = '') => {
|
|
|
132
133
|
if (reports.length) {
|
|
133
134
|
const digesterDir = `${functionDir}/digest/${digesterID}`;
|
|
134
135
|
// Get the digester.
|
|
135
|
-
const digester = require(`${digesterDir}/index`)
|
|
136
|
-
// Get the template.
|
|
137
|
-
const template = await fs.readFile(`${digesterDir}/index.html`, 'utf8');
|
|
136
|
+
const {digester} = require(`${digesterDir}/index`);
|
|
138
137
|
// Digest the reports.
|
|
139
|
-
const digestedReports = digest(
|
|
138
|
+
const digestedReports = await digest(digester, reports);
|
|
140
139
|
const digestedReportDir = `${reportDir}/digested`;
|
|
141
140
|
// For each digested report:
|
|
142
141
|
for (const reportID of Object.keys(digestedReports)) {
|
|
143
142
|
// Save it.
|
|
144
143
|
await fs.writeFile(`${digestedReportDir}/${reportID}.html`, digestedReports[reportID]);
|
|
145
144
|
};
|
|
145
|
+
console.log(`Reports digested and saved in ${digestedReportDir}`);
|
|
146
146
|
}
|
|
147
147
|
// Otherwise, i.e. if no scored reports are to be digested:
|
|
148
148
|
else {
|
package/digest.js
CHANGED
|
@@ -2,27 +2,19 @@
|
|
|
2
2
|
digest.js
|
|
3
3
|
Creates digests from a scored reports.
|
|
4
4
|
Arguments:
|
|
5
|
-
0.
|
|
6
|
-
1.
|
|
7
|
-
2. Array of scored reports.
|
|
5
|
+
0. Digesting function.
|
|
6
|
+
1. Array of scored reports.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
// ########## FUNCTIONS
|
|
11
10
|
|
|
12
|
-
// Replaces the placeholders in content with eponymous query parameters.
|
|
13
|
-
const replaceHolders = (content, query) => content
|
|
14
|
-
.replace(/__([a-zA-Z]+)__/g, (ph, qp) => query[qp]);
|
|
15
11
|
// Digests the scored reports and returns them, digested.
|
|
16
|
-
exports.digest = (
|
|
12
|
+
exports.digest = async (digester, reports) => {
|
|
17
13
|
const digests = {};
|
|
18
|
-
// Create a query.
|
|
19
|
-
const query = {};
|
|
20
14
|
// For each report:
|
|
21
15
|
for (const report of reports) {
|
|
22
|
-
// Populate the query.
|
|
23
|
-
digester(report, query);
|
|
24
16
|
// Use it to create a digest.
|
|
25
|
-
const digestedReport =
|
|
17
|
+
const digestedReport = await digester(report);
|
|
26
18
|
// Add the digest to the array of digests.
|
|
27
19
|
digests[report.id] = digestedReport;
|
|
28
20
|
console.log(`Report ${report.id} digested`);
|
package/package.json
CHANGED
|
@@ -22,28 +22,40 @@
|
|
|
22
22
|
<tr><th>URL</th><td>__url__</td></tr>
|
|
23
23
|
<tr><th>Requester</th><td>__requester__</td></tr>
|
|
24
24
|
<tr><th>Test date</th><td>__dateSlash__</td></tr>
|
|
25
|
-
<tr><th>Score</th><td>
|
|
25
|
+
<tr><th>Score</th><td>__total__</td></tr>
|
|
26
26
|
<tr><th>Tested by</th><td>Testaro, procedure <code>__ts__</code></td></tr>
|
|
27
27
|
<tr><th>Scored by</th><td>Testilo, procedure <code>__sp__</code></td></tr>
|
|
28
28
|
<tr><th>Digested by</th><td>Testilo, procedure <code>__dp__</code></td></tr>
|
|
29
29
|
</table>
|
|
30
30
|
</header>
|
|
31
31
|
<h2>Introduction</h2>
|
|
32
|
-
<p>This is a digest of results from a battery of accessibility
|
|
33
|
-
<p>The battery includes
|
|
34
|
-
<p>These tests were run on the web page named above and gave the page a score of
|
|
35
|
-
<h2>
|
|
32
|
+
<p>This is a digest of results from a battery of accessibility tests.</p>
|
|
33
|
+
<p>The battery includes about 1350 tests drawn from ten different packages: Alfa, Axe, Continuum, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Tenon, Testaro, and WAVE.</p>
|
|
34
|
+
<p>These tests were run on the web page named above and gave the page a score of __total__, where 0 would be <q>perfect</q>.</p>
|
|
35
|
+
<h2>Scores</h2>
|
|
36
36
|
<table class="allBorder secondCellRight">
|
|
37
|
-
<caption>Score
|
|
37
|
+
<caption>Score summary</caption>
|
|
38
|
+
<thead>
|
|
39
|
+
<tr><th>Component</th><th>Score</th></tr>
|
|
40
|
+
</thead>
|
|
38
41
|
<tbody class="headersLeft">
|
|
39
|
-
|
|
42
|
+
<tr><th>total</th><td>__total__</td></tr>
|
|
43
|
+
<tr><th>issue</th><td>__issue__</td></tr>
|
|
44
|
+
<tr><th>tool</th><td>__tool__</td></tr>
|
|
45
|
+
<tr><th>prevention</th><td>__prevention__</td></tr>
|
|
46
|
+
<tr><th>log</th><td>__log__</td></tr>
|
|
47
|
+
<tr><th>latency</th><td>__latency__</td></tr>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
<table class="allBorder secondCellRight">
|
|
51
|
+
<caption>Issue scores</caption>
|
|
52
|
+
<thead>
|
|
53
|
+
<tr><th>Issue</th><th>Score</th></tr>
|
|
54
|
+
</thead>
|
|
55
|
+
<tbody class="headersLeft">
|
|
56
|
+
__issueRows__
|
|
40
57
|
</tbody>
|
|
41
58
|
</table>
|
|
42
|
-
<h2>Issue summary</h2>
|
|
43
|
-
<h3>Special issues</h3>
|
|
44
|
-
__specialSummary__
|
|
45
|
-
<h3>Classified issues</h3>
|
|
46
|
-
__issueSummary__
|
|
47
59
|
<h2>Complete report</h2>
|
|
48
60
|
<pre>__report__</pre>
|
|
49
61
|
<footer>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
index: digester for scoring procedure tsp24.
|
|
3
|
-
Creator of parameters for substitution into index.html.
|
|
4
|
-
*/
|
|
1
|
+
// index: digester for scoring procedure tsp27.
|
|
5
2
|
|
|
6
3
|
// IMPORTS
|
|
7
4
|
|
|
5
|
+
// Issue classification
|
|
8
6
|
const {issueClasses} = require('../../score/tic27');
|
|
7
|
+
// Function to process files.
|
|
8
|
+
const fs = require('fs/promises');
|
|
9
9
|
|
|
10
10
|
// CONSTANTS
|
|
11
11
|
|
|
@@ -21,21 +21,15 @@ const innerJoiner = '\n ';
|
|
|
21
21
|
const htmlEscape = textOrNumber => textOrNumber
|
|
22
22
|
.toString()
|
|
23
23
|
.replace(/&/g, '&')
|
|
24
|
-
.replace(/</g, '<')
|
|
24
|
+
.replace(/</g, '<')
|
|
25
|
+
.replace(/"/g, '"');
|
|
25
26
|
// Gets a row of the score-summary table.
|
|
26
27
|
const getScoreRow = (componentName, score) => `<tr><th>${componentName}</th><td>${score}</td></tr>`;
|
|
27
|
-
// Gets the start of a paragraph about a special score.
|
|
28
|
-
const getSpecialPStart = (summary, scoreID) =>
|
|
29
|
-
`<p><span class="componentID">${scoreID}</span>: Score ${summary[scoreID]}.`;
|
|
30
28
|
// Adds parameters to a query for a digest.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const {acts, sources, jobData, score} = report;
|
|
29
|
+
const populateQuery = (report, query) => {
|
|
30
|
+
const {sources, jobData, score} = report;
|
|
34
31
|
const {script, target, requester} = sources;
|
|
35
|
-
const
|
|
36
|
-
const reportJSONSafe = htmlEscape(reportJSON);
|
|
37
|
-
query.report = reportJSONSafe;
|
|
38
|
-
const {scoreProcID, summary, issues} = score;
|
|
32
|
+
const {scoreProcID, summary, details} = score;
|
|
39
33
|
const {total} = summary;
|
|
40
34
|
query.ts = script;
|
|
41
35
|
query.sp = scoreProcID;
|
|
@@ -46,37 +40,37 @@ exports.makeQuery = (report, query) => {
|
|
|
46
40
|
query.org = target.what;
|
|
47
41
|
query.url = target.which;
|
|
48
42
|
query.requester = requester;
|
|
49
|
-
// Add the
|
|
50
|
-
|
|
51
|
-
query
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
// Get rows for a score-summary table.
|
|
58
|
-
const scoreRows = [];
|
|
43
|
+
// Add values for the score-summary table to the query.
|
|
44
|
+
['total', 'issue', 'tool', 'prevention', 'log', 'latency'].forEach(sumItem => {
|
|
45
|
+
query[sumItem] = summary[sumItem];
|
|
46
|
+
});
|
|
47
|
+
const rows = {
|
|
48
|
+
summaryRows: [],
|
|
49
|
+
issueRows: []
|
|
50
|
+
};
|
|
59
51
|
const componentIDs = ['issues', 'tools', 'preventions', 'log', 'latency'];
|
|
60
52
|
['total'].concat(componentIDs).forEach(itemID => {
|
|
61
53
|
if (summary[itemID]) {
|
|
62
|
-
|
|
54
|
+
rows.summaryRows.push(getScoreRow(itemID, summary[itemID]));
|
|
63
55
|
}
|
|
64
56
|
});
|
|
65
|
-
//
|
|
66
|
-
Object.keys(
|
|
67
|
-
|
|
57
|
+
// Get rows for an issue-score table.
|
|
58
|
+
Object.keys(details.issue).forEach(issueID => {
|
|
59
|
+
rows.issueRows.push(getScoreRow(issueID, details.issue[issueID].score));
|
|
68
60
|
});
|
|
69
61
|
// Add the rows to the query.
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
['summaryRows', 'issueRows'].forEach(rowType => {
|
|
63
|
+
query[rowType] = rows[rowType].join(innerJoiner);
|
|
64
|
+
});
|
|
65
|
+
// Add paragraphs about the issues to the query.
|
|
72
66
|
const issueSummaryItems = [];
|
|
73
|
-
Object.keys(
|
|
67
|
+
Object.keys(details.issue).forEach(issueID => {
|
|
74
68
|
const issueHeading = `<h4>Issue ${issueID}</h4>`;
|
|
75
69
|
const wcagP = `<p>WCAG: ${issueClasses[issueID].wcag || 'N/A'}</p>`;
|
|
76
|
-
const scoreP = `<p>Score: ${
|
|
70
|
+
const scoreP = `<p>Score: ${details.issue[issueID]}</p>`;
|
|
77
71
|
const issueIntroP = '<p>Issue reports in this category:</p>';
|
|
78
72
|
const issueListItems = [];
|
|
79
|
-
const issueData =
|
|
73
|
+
const issueData = details.issue[issueID];
|
|
80
74
|
const toolIDs = Object.keys(issueData.tools);
|
|
81
75
|
toolIDs.forEach(toolID => {
|
|
82
76
|
const testIDs = Object.keys(issueData.tools[toolID]);
|
|
@@ -96,4 +90,22 @@ exports.makeQuery = (report, query) => {
|
|
|
96
90
|
issueSummaryItems.push(issueHeading, wcagP, scoreP, issueIntroP, issueList);
|
|
97
91
|
});
|
|
98
92
|
query.issueSummary = issueSummaryItems.join(joiner);
|
|
93
|
+
// Add an HTML-safe copy of the report to the query to be appended to the digest.
|
|
94
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
95
|
+
const reportJSONSafe = htmlEscape(reportJSON);
|
|
96
|
+
query.report = reportJSONSafe;
|
|
97
|
+
};
|
|
98
|
+
// Returns a digested report.
|
|
99
|
+
exports.digester = async report => {
|
|
100
|
+
// Create a query to replace plateholders.
|
|
101
|
+
const query = {};
|
|
102
|
+
populateQuery(report, query);
|
|
103
|
+
// Get the template.
|
|
104
|
+
let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
|
|
105
|
+
// Replace its placeholders.
|
|
106
|
+
Object.keys(query).forEach(param => {
|
|
107
|
+
template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
108
|
+
});
|
|
109
|
+
// Return the digest.
|
|
110
|
+
return template;
|
|
99
111
|
};
|
package/procs/score/tic27.js
CHANGED
|
@@ -2577,13 +2577,6 @@ exports.issueClasses = {
|
|
|
2577
2577
|
quality: 1,
|
|
2578
2578
|
what: 'role attribute has an invalid value'
|
|
2579
2579
|
}
|
|
2580
|
-
},
|
|
2581
|
-
testaro: {
|
|
2582
|
-
'role-bad': {
|
|
2583
|
-
variable: false,
|
|
2584
|
-
quality: 0.5,
|
|
2585
|
-
what: 'Nonexistent or implicit-overriding role'
|
|
2586
|
-
}
|
|
2587
2580
|
}
|
|
2588
2581
|
}
|
|
2589
2582
|
},
|
|
@@ -2621,10 +2614,10 @@ exports.issueClasses = {
|
|
|
2621
2614
|
}
|
|
2622
2615
|
},
|
|
2623
2616
|
testaro: {
|
|
2624
|
-
'role
|
|
2617
|
+
'role': {
|
|
2625
2618
|
variable: false,
|
|
2626
2619
|
quality: 1,
|
|
2627
|
-
what: '
|
|
2620
|
+
what: 'Invalid, native-replacing, or redundant role'
|
|
2628
2621
|
}
|
|
2629
2622
|
}
|
|
2630
2623
|
}
|
|
@@ -4696,13 +4689,6 @@ exports.issueClasses = {
|
|
|
4696
4689
|
what: 'Form control has no accessible name'
|
|
4697
4690
|
}
|
|
4698
4691
|
},
|
|
4699
|
-
testaro: {
|
|
4700
|
-
'labClash-unlabeled': {
|
|
4701
|
-
variable: false,
|
|
4702
|
-
quality: 1,
|
|
4703
|
-
what: 'Button, input, select, or textarea is unlabeled'
|
|
4704
|
-
}
|
|
4705
|
-
},
|
|
4706
4692
|
wave: {
|
|
4707
4693
|
'label_missing': {
|
|
4708
4694
|
variable: false,
|
|
@@ -4967,23 +4953,10 @@ exports.issueClasses = {
|
|
|
4967
4953
|
}
|
|
4968
4954
|
},
|
|
4969
4955
|
testaro: {
|
|
4970
|
-
'focInd
|
|
4956
|
+
'focInd': {
|
|
4971
4957
|
variable: false,
|
|
4972
4958
|
quality: 1,
|
|
4973
|
-
what: 'Focused element displays no focus indicator'
|
|
4974
|
-
}
|
|
4975
|
-
}
|
|
4976
|
-
}
|
|
4977
|
-
},
|
|
4978
|
-
focusIndicationBad: {
|
|
4979
|
-
wcag: '2.4.7',
|
|
4980
|
-
weight: 3,
|
|
4981
|
-
tools: {
|
|
4982
|
-
testaro: {
|
|
4983
|
-
'focInd-nonoutline': {
|
|
4984
|
-
variable: false,
|
|
4985
|
-
quality: 1,
|
|
4986
|
-
what: 'Focused element displays a nostandard focus indicator'
|
|
4959
|
+
what: 'Focused element displays a nonstandard or no focus indicator'
|
|
4987
4960
|
}
|
|
4988
4961
|
}
|
|
4989
4962
|
}
|
|
@@ -5438,15 +5411,10 @@ exports.issueClasses = {
|
|
|
5438
5411
|
weight: 3,
|
|
5439
5412
|
tools: {
|
|
5440
5413
|
testaro: {
|
|
5441
|
-
'focOp
|
|
5414
|
+
'focOp': {
|
|
5442
5415
|
variable: false,
|
|
5443
5416
|
quality: 1,
|
|
5444
|
-
what: 'Tab-focusable elements that are inoperable'
|
|
5445
|
-
},
|
|
5446
|
-
'focOp-operable-nonfocusable': {
|
|
5447
|
-
variable: false,
|
|
5448
|
-
quality: 1,
|
|
5449
|
-
what: 'Operable elements that cannot be Tab-focused'
|
|
5417
|
+
what: 'Tab-focusable elements that are inoperable or operable elements that are not focusable'
|
|
5450
5418
|
}
|
|
5451
5419
|
}
|
|
5452
5420
|
}
|
|
@@ -5645,12 +5613,12 @@ exports.issueClasses = {
|
|
|
5645
5613
|
}
|
|
5646
5614
|
}
|
|
5647
5615
|
},
|
|
5648
|
-
|
|
5616
|
+
hoverSurprise: {
|
|
5649
5617
|
wcag: '1.4.13',
|
|
5650
5618
|
weight: 3,
|
|
5651
5619
|
tools: {
|
|
5652
5620
|
testaro: {
|
|
5653
|
-
'hover
|
|
5621
|
+
'hover': {
|
|
5654
5622
|
variable: false,
|
|
5655
5623
|
quality: 1,
|
|
5656
5624
|
what: 'Hovering over element has unexpected effects'
|
|
@@ -5658,71 +5626,6 @@ exports.issueClasses = {
|
|
|
5658
5626
|
}
|
|
5659
5627
|
}
|
|
5660
5628
|
},
|
|
5661
|
-
unhoverable: {
|
|
5662
|
-
wcag: '1.4.13',
|
|
5663
|
-
weight: 3,
|
|
5664
|
-
tools: {
|
|
5665
|
-
testaro: {
|
|
5666
|
-
'hover-unhoverables': {
|
|
5667
|
-
variable: false,
|
|
5668
|
-
quality: 1,
|
|
5669
|
-
what: 'Operable element cannot be hovered over'
|
|
5670
|
-
}
|
|
5671
|
-
}
|
|
5672
|
-
}
|
|
5673
|
-
},
|
|
5674
|
-
hoverNoCursor: {
|
|
5675
|
-
wcag: '1.4.13',
|
|
5676
|
-
weight: 3,
|
|
5677
|
-
tools: {
|
|
5678
|
-
testaro: {
|
|
5679
|
-
'hover-noCursors': {
|
|
5680
|
-
variable: false,
|
|
5681
|
-
quality: 1,
|
|
5682
|
-
what: 'Hoverable element hides the mouse cursor'
|
|
5683
|
-
}
|
|
5684
|
-
}
|
|
5685
|
-
}
|
|
5686
|
-
},
|
|
5687
|
-
hoverBadCursor: {
|
|
5688
|
-
wcag: '1.4.13',
|
|
5689
|
-
weight: 2,
|
|
5690
|
-
tools: {
|
|
5691
|
-
testaro: {
|
|
5692
|
-
'hover-badCursors': {
|
|
5693
|
-
variable: false,
|
|
5694
|
-
quality: 1,
|
|
5695
|
-
what: 'Link or button makes the hovering mouse cursor nonstandard'
|
|
5696
|
-
}
|
|
5697
|
-
}
|
|
5698
|
-
}
|
|
5699
|
-
},
|
|
5700
|
-
hoverNoIndicator: {
|
|
5701
|
-
wcag: '1.4.13',
|
|
5702
|
-
weight: 3,
|
|
5703
|
-
tools: {
|
|
5704
|
-
testaro: {
|
|
5705
|
-
'hover-noCursors': {
|
|
5706
|
-
variable: false,
|
|
5707
|
-
quality: 1,
|
|
5708
|
-
what: 'Button shows no indication of being hovered over'
|
|
5709
|
-
}
|
|
5710
|
-
}
|
|
5711
|
-
}
|
|
5712
|
-
},
|
|
5713
|
-
hoverBadIndicator: {
|
|
5714
|
-
wcag: '1.4.13',
|
|
5715
|
-
weight: 2,
|
|
5716
|
-
tools: {
|
|
5717
|
-
testaro: {
|
|
5718
|
-
'hover-noCursors': {
|
|
5719
|
-
variable: false,
|
|
5720
|
-
quality: 1,
|
|
5721
|
-
what: 'List item changes when hovered over'
|
|
5722
|
-
}
|
|
5723
|
-
}
|
|
5724
|
-
}
|
|
5725
|
-
},
|
|
5726
5629
|
labelClash: {
|
|
5727
5630
|
wcag: '1.3.1',
|
|
5728
5631
|
weight: 2,
|
|
@@ -5735,7 +5638,7 @@ exports.issueClasses = {
|
|
|
5735
5638
|
}
|
|
5736
5639
|
},
|
|
5737
5640
|
testaro: {
|
|
5738
|
-
'labClash
|
|
5641
|
+
'labClash': {
|
|
5739
5642
|
variable: false,
|
|
5740
5643
|
quality: 1,
|
|
5741
5644
|
what: 'Incompatible label types'
|
package/procs/score/tsp27.js
CHANGED
|
@@ -63,23 +63,32 @@ exports.scorer = report => {
|
|
|
63
63
|
scoreProcID,
|
|
64
64
|
summary: {
|
|
65
65
|
total: 0,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
issue: 0,
|
|
67
|
+
tool: 0,
|
|
68
|
+
prevention: 0,
|
|
69
69
|
log: 0,
|
|
70
70
|
latency: 0
|
|
71
71
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
details: {
|
|
73
|
+
severity: {
|
|
74
|
+
total: [0, 0, 0, 0],
|
|
75
|
+
byTool: {}
|
|
76
|
+
},
|
|
77
|
+
prevention: {},
|
|
78
|
+
issue: {}
|
|
79
|
+
}
|
|
76
80
|
};
|
|
77
|
-
const {summary,
|
|
81
|
+
const {summary, details} = score;
|
|
78
82
|
// For each test act:
|
|
79
83
|
testActs.forEach(act => {
|
|
80
|
-
// If
|
|
84
|
+
// If the page prevented the tool from operating:
|
|
81
85
|
const {which, standardResult} = act;
|
|
82
|
-
if (
|
|
86
|
+
if (standardResult.prevented) {
|
|
87
|
+
// Add this to the score.
|
|
88
|
+
details.prevention[which] = preventionWeight;
|
|
89
|
+
}
|
|
90
|
+
// Otherwise, if a successful standard result exists:
|
|
91
|
+
else if (
|
|
83
92
|
standardResult
|
|
84
93
|
&& standardResult.totals
|
|
85
94
|
&& standardResult.totals.length === 4
|
|
@@ -87,16 +96,11 @@ exports.scorer = report => {
|
|
|
87
96
|
) {
|
|
88
97
|
// Add the severity totals of the tool to the score.
|
|
89
98
|
const {totals} = standardResult;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
toolTotals[index] += totals[index];
|
|
93
|
-
});
|
|
94
|
-
// Update the issue totals for the tool.
|
|
95
|
-
const issueTotals = {};
|
|
96
|
-
let ruleID;
|
|
99
|
+
details.severity.byTool[which] = totals;
|
|
100
|
+
// Add the instance data of the tool to the score.
|
|
97
101
|
standardResult.instances.forEach(instance => {
|
|
98
|
-
ruleID =
|
|
99
|
-
if (! ruleID) {
|
|
102
|
+
let {ruleID} = instance;
|
|
103
|
+
if (! issueIndex[which][ruleID]) {
|
|
100
104
|
ruleID = issueMatcher.find(pattern => {
|
|
101
105
|
const patternRE = new RegExp(pattern);
|
|
102
106
|
return patternRE.test(instance.ruleID);
|
|
@@ -104,40 +108,92 @@ exports.scorer = report => {
|
|
|
104
108
|
}
|
|
105
109
|
if (ruleID) {
|
|
106
110
|
const issueID = issueIndex[which][ruleID];
|
|
107
|
-
if (!
|
|
108
|
-
|
|
111
|
+
if (! details.issue[issueID]) {
|
|
112
|
+
details.issue[issueID] = {
|
|
113
|
+
score: 0,
|
|
114
|
+
maxCount: 0,
|
|
115
|
+
weight: issueClasses[issueID].weight,
|
|
116
|
+
tools: {}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (! details.issue[issueID].tools[which]) {
|
|
120
|
+
details.issue[issueID].tools[which] = {};
|
|
121
|
+
}
|
|
122
|
+
if (! details.issue[issueID].tools[which][ruleID]) {
|
|
123
|
+
details.issue[issueID].tools[which][ruleID] = {
|
|
124
|
+
quality: issueClasses[issueID].tools[which][ruleID].quality,
|
|
125
|
+
complaints: {
|
|
126
|
+
countTotal: 0,
|
|
127
|
+
texts: []
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
details
|
|
132
|
+
.issue[issueID]
|
|
133
|
+
.tools[which][ruleID]
|
|
134
|
+
.complaints
|
|
135
|
+
.countTotal += instance.count || 1;
|
|
136
|
+
if (
|
|
137
|
+
! details
|
|
138
|
+
.issue[issueID]
|
|
139
|
+
.tools[which][ruleID]
|
|
140
|
+
.complaints
|
|
141
|
+
.texts
|
|
142
|
+
.includes(instance.what)
|
|
143
|
+
) {
|
|
144
|
+
details.issue[issueID].tools[which][ruleID].complaints.texts.push(instance.what);
|
|
109
145
|
}
|
|
110
|
-
issueTotals[issueID] += instance.count || 1;
|
|
111
146
|
}
|
|
112
147
|
else {
|
|
113
148
|
console.log(`ERROR: ${instance.ruleID} of ${which} not found in issueClasses`);
|
|
114
149
|
}
|
|
115
150
|
});
|
|
116
|
-
// Update the issue totals in the score.
|
|
117
|
-
Object.keys(issueTotals).forEach(issueID => {
|
|
118
|
-
issues[issueID] = Math.max(issues[id] || 0, issueTotals[issueID]);
|
|
119
|
-
});
|
|
120
|
-
summary.issues = Object.values(issues).reduce((total, current) => total + current);
|
|
121
151
|
}
|
|
122
|
-
// Otherwise, i.e. if no
|
|
152
|
+
// Otherwise, i.e. if no successful standard result exists:
|
|
123
153
|
else {
|
|
124
|
-
// Add
|
|
125
|
-
|
|
126
|
-
act.result = {};
|
|
127
|
-
}
|
|
128
|
-
if (! act.result.prevented) {
|
|
129
|
-
act.result.prevented = true;
|
|
130
|
-
};
|
|
131
|
-
// Add the tool and the prevention score to the score.
|
|
132
|
-
preventions[which] = preventionWeight;
|
|
133
|
-
summary.preventions += preventionWeight;
|
|
154
|
+
// Add an inferred prevention to the score.
|
|
155
|
+
details.prevention[which] = preventionWeight;
|
|
134
156
|
}
|
|
135
157
|
});
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
// For each issue with any complaints:
|
|
159
|
+
Object.keys(details.issue).forEach(issueID => {
|
|
160
|
+
const issueData = details.issue[issueID];
|
|
161
|
+
// For each tool with any complaints for the issue:
|
|
162
|
+
Object.keys(issueData.tools).forEach(toolID => {
|
|
163
|
+
// Get the sum of the weighted counts of its issue rules.
|
|
164
|
+
let weightedCount = 0;
|
|
165
|
+
Object.values(issueData.tools[toolID]).forEach(ruleData => {
|
|
166
|
+
weightedCount += ruleData.quality * ruleData.complaints.countTotal;
|
|
167
|
+
});
|
|
168
|
+
// If the sum exceeds the existing maximum weighted count for the issue:
|
|
169
|
+
if (weightedCount > issueData.maxCount) {
|
|
170
|
+
// Change the maximum count for the issue to the sum.
|
|
171
|
+
issueData.maxCount = weightedCount;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
// Get the score for the issue.
|
|
175
|
+
issueData.score = issueData.weight * issueData.maxCount;
|
|
176
|
+
});
|
|
177
|
+
// Add the severity detail totals to the score.
|
|
178
|
+
details.severity.total = Object.keys(details.severity.byTool).reduce((severityTotals, toolID) => {
|
|
179
|
+
details.severity.byTool[toolID].forEach((severityScore, index) => {
|
|
180
|
+
severityTotals[index] += severityScore;
|
|
181
|
+
});
|
|
182
|
+
return severityTotals;
|
|
183
|
+
}, details.severity.total);
|
|
184
|
+
// Add the summary issue total to the score.
|
|
185
|
+
summary.issue = Object
|
|
186
|
+
.values(details.issue)
|
|
187
|
+
.reduce((total, current) => total + current.score, 0);
|
|
188
|
+
// Add the summary tool total to the score.
|
|
189
|
+
summary.tool = toolWeight * details.severity.total.reduce(
|
|
190
|
+
(total, current, index) => total + severityWeights[index] * current, 0
|
|
191
|
+
);
|
|
192
|
+
// Add the summary prevention total to the score.
|
|
193
|
+
summary.prevention = Object.values(details.prevention).reduce(
|
|
194
|
+
(total, current) => total + current, 0
|
|
139
195
|
);
|
|
140
|
-
// Add the log score to the score.
|
|
196
|
+
// Add the summary log score to the score.
|
|
141
197
|
const {jobData} = report;
|
|
142
198
|
summary.log = Math.max(0, Math.round(
|
|
143
199
|
logWeights.logCount * jobData.logCount
|
|
@@ -147,21 +203,21 @@ exports.scorer = report => {
|
|
|
147
203
|
+ logWeights.prohibitedCount * jobData.prohibitedCount +
|
|
148
204
|
+ logWeights.visitRejectionCount * jobData.visitRejectionCount
|
|
149
205
|
));
|
|
150
|
-
// Add the latency score to the score.
|
|
206
|
+
// Add the summary latency score to the score.
|
|
151
207
|
summary.latency = Math.round(
|
|
152
208
|
latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
|
|
153
209
|
);
|
|
154
|
-
// Round the scores.
|
|
210
|
+
// Round the unrounded scores.
|
|
155
211
|
Object.keys(summary).forEach(summaryTypeName => {
|
|
156
212
|
summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
|
|
157
213
|
});
|
|
158
|
-
|
|
159
|
-
|
|
214
|
+
details.severity.total.forEach((severityTotal, index) => {
|
|
215
|
+
details.severity.total[index] = Math.round(severityTotal);
|
|
160
216
|
});
|
|
161
|
-
// Add the total score to the score.
|
|
162
|
-
summary.total = summary.
|
|
163
|
-
+ summary.
|
|
164
|
-
+ summary.
|
|
217
|
+
// Add the summary total score to the score.
|
|
218
|
+
summary.total = summary.issue
|
|
219
|
+
+ summary.tool
|
|
220
|
+
+ summary.prevention
|
|
165
221
|
+ summary.log
|
|
166
222
|
+ summary.latency;
|
|
167
223
|
// Add the score to the report.
|