testilo 11.0.6 → 11.0.7
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/{tic26.js → tic27.js} +166 -8
- package/procs/score/tsp27.js +180 -0
- package/scripts/ts26.json +97 -0
- package/procs/score/tsp24.js +0 -740
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- what: description of the test.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
exports.
|
|
20
|
+
exports.issueClasses = {
|
|
21
21
|
ignorable: {
|
|
22
22
|
wcag: '',
|
|
23
23
|
weight: 0,
|
|
@@ -2586,7 +2586,7 @@ exports.issues = {
|
|
|
2586
2586
|
}
|
|
2587
2587
|
},
|
|
2588
2588
|
testaro: {
|
|
2589
|
-
role: {
|
|
2589
|
+
'role-bad': {
|
|
2590
2590
|
variable: false,
|
|
2591
2591
|
quality: 1,
|
|
2592
2592
|
what: 'Nonexistent or implicit-overriding role'
|
|
@@ -2626,6 +2626,13 @@ exports.issues = {
|
|
|
2626
2626
|
quality: 1,
|
|
2627
2627
|
what: 'explicit role is redundant for a text-type input element without a list attribute'
|
|
2628
2628
|
}
|
|
2629
|
+
},
|
|
2630
|
+
testaro: {
|
|
2631
|
+
'role-redundant': {
|
|
2632
|
+
variable: false,
|
|
2633
|
+
quality: 1,
|
|
2634
|
+
what: 'Redundant role'
|
|
2635
|
+
}
|
|
2629
2636
|
}
|
|
2630
2637
|
}
|
|
2631
2638
|
},
|
|
@@ -2799,20 +2806,25 @@ exports.issues = {
|
|
|
2799
2806
|
}
|
|
2800
2807
|
},
|
|
2801
2808
|
continuum: {
|
|
2809
|
+
1039: {
|
|
2810
|
+
variable: false,
|
|
2811
|
+
quality: 1,
|
|
2812
|
+
what: 'Element with a checkbox role has no aria-checked attribute'
|
|
2813
|
+
},
|
|
2802
2814
|
1040: {
|
|
2803
2815
|
variable: false,
|
|
2804
2816
|
quality: 1,
|
|
2805
|
-
what: '
|
|
2817
|
+
what: 'Element with a combobox role has no aria-controls or no aria-expanded attribute'
|
|
2806
2818
|
},
|
|
2807
2819
|
1042: {
|
|
2808
2820
|
variable: false,
|
|
2809
2821
|
quality: 1,
|
|
2810
|
-
what: '
|
|
2822
|
+
what: 'Element with an option role has no aria-selected attribute'
|
|
2811
2823
|
},
|
|
2812
2824
|
1043: {
|
|
2813
2825
|
variable: false,
|
|
2814
2826
|
quality: 1,
|
|
2815
|
-
what: '
|
|
2827
|
+
what: 'Element with a radio role has no aria-checked attribute'
|
|
2816
2828
|
}
|
|
2817
2829
|
},
|
|
2818
2830
|
nuVal: {
|
|
@@ -3220,6 +3232,11 @@ exports.issues = {
|
|
|
3220
3232
|
variable: false,
|
|
3221
3233
|
quality: 1,
|
|
3222
3234
|
what: 'Contrast ratio of text with background does not meet WCAG 2.1 AA'
|
|
3235
|
+
},
|
|
3236
|
+
text_contrast_sufficient: {
|
|
3237
|
+
variable: false,
|
|
3238
|
+
quality: 1,
|
|
3239
|
+
what: 'Text has a contrast with its background less than the WCAG AA minimum for its size and weight'
|
|
3223
3240
|
}
|
|
3224
3241
|
},
|
|
3225
3242
|
qualWeb: {
|
|
@@ -3307,6 +3324,11 @@ exports.issues = {
|
|
|
3307
3324
|
quality: 1,
|
|
3308
3325
|
what: 'Inline background color may lack a complementary foreground color'
|
|
3309
3326
|
},
|
|
3327
|
+
'AAA.1_4_6.G18.BgImage': {
|
|
3328
|
+
variable: false,
|
|
3329
|
+
quality: 1,
|
|
3330
|
+
what: 'Contrast between the text and the background image may be less than 4.5:1'
|
|
3331
|
+
},
|
|
3310
3332
|
'AAA.1_4_3_F24.F24.FGColour': {
|
|
3311
3333
|
variable: false,
|
|
3312
3334
|
quality: 1,
|
|
@@ -4115,6 +4137,13 @@ exports.issues = {
|
|
|
4115
4137
|
wcag: '1.3.1',
|
|
4116
4138
|
weight: 1,
|
|
4117
4139
|
tools: {
|
|
4140
|
+
qualWeb: {
|
|
4141
|
+
'QW-BP23': {
|
|
4142
|
+
variable: false,
|
|
4143
|
+
quality: 1,
|
|
4144
|
+
what: 'List item is used nonsemantically'
|
|
4145
|
+
}
|
|
4146
|
+
},
|
|
4118
4147
|
wave: {
|
|
4119
4148
|
'list_possible': {
|
|
4120
4149
|
variable: false,
|
|
@@ -4681,6 +4710,13 @@ exports.issues = {
|
|
|
4681
4710
|
what: 'Form control has no accessible name'
|
|
4682
4711
|
}
|
|
4683
4712
|
},
|
|
4713
|
+
testaro: {
|
|
4714
|
+
'labClash-unlabeled': {
|
|
4715
|
+
variable: false,
|
|
4716
|
+
quality: 1,
|
|
4717
|
+
what: 'Button, input, select, or textarea is unlabeled'
|
|
4718
|
+
}
|
|
4719
|
+
},
|
|
4684
4720
|
wave: {
|
|
4685
4721
|
'label_missing': {
|
|
4686
4722
|
variable: false,
|
|
@@ -4742,6 +4778,11 @@ exports.issues = {
|
|
|
4742
4778
|
}
|
|
4743
4779
|
},
|
|
4744
4780
|
ibm: {
|
|
4781
|
+
label_name_visible: {
|
|
4782
|
+
variable: false,
|
|
4783
|
+
quality: 1,
|
|
4784
|
+
what: 'Accessible name does not match or contain the visible label text'
|
|
4785
|
+
},
|
|
4745
4786
|
WCAG21_Label_Accessible: {
|
|
4746
4787
|
variable: false,
|
|
4747
4788
|
quality: 1,
|
|
@@ -4940,10 +4981,23 @@ exports.issues = {
|
|
|
4940
4981
|
}
|
|
4941
4982
|
},
|
|
4942
4983
|
testaro: {
|
|
4943
|
-
focInd: {
|
|
4984
|
+
'focInd-missing': {
|
|
4944
4985
|
variable: false,
|
|
4945
4986
|
quality: 1,
|
|
4946
|
-
what: 'Focused element displays no
|
|
4987
|
+
what: 'Focused element displays no focus indicator'
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
},
|
|
4992
|
+
focusIndicationBad: {
|
|
4993
|
+
wcag: '2.4.7',
|
|
4994
|
+
weight: 3,
|
|
4995
|
+
tools: {
|
|
4996
|
+
testaro: {
|
|
4997
|
+
'focInd-nonoutline': {
|
|
4998
|
+
variable: false,
|
|
4999
|
+
quality: 1,
|
|
5000
|
+
what: 'Focused element displays a nostandard focus indicator'
|
|
4947
5001
|
}
|
|
4948
5002
|
}
|
|
4949
5003
|
}
|
|
@@ -5605,6 +5659,84 @@ exports.issues = {
|
|
|
5605
5659
|
}
|
|
5606
5660
|
}
|
|
5607
5661
|
},
|
|
5662
|
+
hoverImpact: {
|
|
5663
|
+
wcag: '1.4.13',
|
|
5664
|
+
weight: 3,
|
|
5665
|
+
tools: {
|
|
5666
|
+
testaro: {
|
|
5667
|
+
'hover-impactTriggers': {
|
|
5668
|
+
variable: false,
|
|
5669
|
+
quality: 1,
|
|
5670
|
+
what: 'Hovering over element has unexpected effects'
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
},
|
|
5675
|
+
unhoverable: {
|
|
5676
|
+
wcag: '1.4.13',
|
|
5677
|
+
weight: 3,
|
|
5678
|
+
tools: {
|
|
5679
|
+
testaro: {
|
|
5680
|
+
'hover-unhoverables': {
|
|
5681
|
+
variable: false,
|
|
5682
|
+
quality: 1,
|
|
5683
|
+
what: 'Operable element cannot be hovered over'
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
},
|
|
5688
|
+
hoverNoCursor: {
|
|
5689
|
+
wcag: '1.4.13',
|
|
5690
|
+
weight: 3,
|
|
5691
|
+
tools: {
|
|
5692
|
+
testaro: {
|
|
5693
|
+
'hover-noCursors': {
|
|
5694
|
+
variable: false,
|
|
5695
|
+
quality: 1,
|
|
5696
|
+
what: 'Hoverable element hides the mouse cursor'
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
},
|
|
5701
|
+
hoverBadCursor: {
|
|
5702
|
+
wcag: '1.4.13',
|
|
5703
|
+
weight: 2,
|
|
5704
|
+
tools: {
|
|
5705
|
+
testaro: {
|
|
5706
|
+
'hover-badCursors': {
|
|
5707
|
+
variable: false,
|
|
5708
|
+
quality: 1,
|
|
5709
|
+
what: 'Link or button makes the hovering mouse cursor nonstandard'
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
},
|
|
5714
|
+
hoverNoIndicator: {
|
|
5715
|
+
wcag: '1.4.13',
|
|
5716
|
+
weight: 3,
|
|
5717
|
+
tools: {
|
|
5718
|
+
testaro: {
|
|
5719
|
+
'hover-noCursors': {
|
|
5720
|
+
variable: false,
|
|
5721
|
+
quality: 1,
|
|
5722
|
+
what: 'Button shows no indication of being hovered over'
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
},
|
|
5727
|
+
hoverBadIndicator: {
|
|
5728
|
+
wcag: '1.4.13',
|
|
5729
|
+
weight: 2,
|
|
5730
|
+
tools: {
|
|
5731
|
+
testaro: {
|
|
5732
|
+
'hover-noCursors': {
|
|
5733
|
+
variable: false,
|
|
5734
|
+
quality: 1,
|
|
5735
|
+
what: 'List item changes when hovered over'
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
},
|
|
5608
5740
|
labelClash: {
|
|
5609
5741
|
wcag: '1.3.1',
|
|
5610
5742
|
weight: 2,
|
|
@@ -5617,7 +5749,7 @@ exports.issues = {
|
|
|
5617
5749
|
}
|
|
5618
5750
|
},
|
|
5619
5751
|
testaro: {
|
|
5620
|
-
labClash: {
|
|
5752
|
+
'labClash-mislabeled': {
|
|
5621
5753
|
variable: false,
|
|
5622
5754
|
quality: 1,
|
|
5623
5755
|
what: 'Incompatible label types'
|
|
@@ -5956,6 +6088,19 @@ exports.issues = {
|
|
|
5956
6088
|
}
|
|
5957
6089
|
}
|
|
5958
6090
|
},
|
|
6091
|
+
filterStyle: {
|
|
6092
|
+
wcag: '4.1',
|
|
6093
|
+
weight: 1,
|
|
6094
|
+
tools: {
|
|
6095
|
+
testaro: {
|
|
6096
|
+
filterStyle: {
|
|
6097
|
+
variable: false,
|
|
6098
|
+
quality: 1,
|
|
6099
|
+
what: 'Element styles include filter'
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
},
|
|
5959
6104
|
zIndexNotZero: {
|
|
5960
6105
|
wcag: '1.4',
|
|
5961
6106
|
weight: 1,
|
|
@@ -6917,6 +7062,19 @@ exports.issues = {
|
|
|
6917
7062
|
},
|
|
6918
7063
|
}
|
|
6919
7064
|
},
|
|
7065
|
+
slashParseRisk: {
|
|
7066
|
+
wcag: '4.1',
|
|
7067
|
+
weight: 1,
|
|
7068
|
+
tools: {
|
|
7069
|
+
nuVal: {
|
|
7070
|
+
'Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.': {
|
|
7071
|
+
variable: false,
|
|
7072
|
+
quality: 1,
|
|
7073
|
+
what: 'Void element has a useless trailing slash.'
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
},
|
|
6920
7078
|
encodingBad: {
|
|
6921
7079
|
wcag: '3.1.3',
|
|
6922
7080
|
weight: 4,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*
|
|
2
|
+
tsp27
|
|
3
|
+
Testilo score proc 27
|
|
4
|
+
|
|
5
|
+
Computes target score data and adds them to a ts26 report.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// IMPORTS
|
|
9
|
+
|
|
10
|
+
const {issueClasses} = require('./tic27');
|
|
11
|
+
|
|
12
|
+
// CONSTANTS
|
|
13
|
+
|
|
14
|
+
// ID of this proc.
|
|
15
|
+
const scoreProcID = 'tsp27';
|
|
16
|
+
// Configuration disclosures.
|
|
17
|
+
const severityWeights = [1, 2, 3, 4];
|
|
18
|
+
const toolWeight = 0.1;
|
|
19
|
+
const logWeights = {
|
|
20
|
+
logCount: 0.1,
|
|
21
|
+
logSize: 0.002,
|
|
22
|
+
errorLogCount: 0.2,
|
|
23
|
+
errorLogSize: 0.004,
|
|
24
|
+
prohibitedCount: 3,
|
|
25
|
+
visitRejectionCount: 2
|
|
26
|
+
};
|
|
27
|
+
// How much each second of excess latency adds to the score.
|
|
28
|
+
const latencyWeight = 1;
|
|
29
|
+
// Normal latency (1.5 second per visit).
|
|
30
|
+
const normalLatency = 20;
|
|
31
|
+
// How much each prevention adds to the score.
|
|
32
|
+
const preventionWeight = 200;
|
|
33
|
+
// Indexes of issues.
|
|
34
|
+
const issueIndex = {};
|
|
35
|
+
const issueMatcher = [];
|
|
36
|
+
Object.keys(issueClasses).forEach(issueClassName => {
|
|
37
|
+
Object.keys(issueClasses[issueClassName].tools).forEach(toolName => {
|
|
38
|
+
Object.keys(issueClasses[issueClassName].tools[toolName]).forEach(issueID => {
|
|
39
|
+
if (! issueIndex[toolName]) {
|
|
40
|
+
issueIndex[toolName] = {};
|
|
41
|
+
}
|
|
42
|
+
issueIndex[toolName][issueID] = issueClassName;
|
|
43
|
+
if (issueClasses[issueClassName].tools[toolName][issueID].variable) {
|
|
44
|
+
issueMatcher.push(issueID);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// FUNCTIONS
|
|
51
|
+
|
|
52
|
+
// Scores a report.
|
|
53
|
+
exports.scorer = report => {
|
|
54
|
+
console.log(report.id);
|
|
55
|
+
// If there are any acts in the report:
|
|
56
|
+
const {acts} = report;
|
|
57
|
+
if (Array.isArray(acts) && acts.length) {
|
|
58
|
+
// If any of them are test acts:
|
|
59
|
+
const testActs = acts.filter(act => act.type === 'test');
|
|
60
|
+
if (testActs.length) {
|
|
61
|
+
// Initialize the score data.
|
|
62
|
+
const score = {
|
|
63
|
+
scoreProcID,
|
|
64
|
+
summary: {
|
|
65
|
+
total: 0,
|
|
66
|
+
issues: 0,
|
|
67
|
+
tools: 0,
|
|
68
|
+
preventions: 0,
|
|
69
|
+
log: 0,
|
|
70
|
+
latency: 0
|
|
71
|
+
},
|
|
72
|
+
toolTotals: [0, 0, 0, 0],
|
|
73
|
+
issues: {},
|
|
74
|
+
tools: {},
|
|
75
|
+
preventions: {}
|
|
76
|
+
};
|
|
77
|
+
const {summary, toolTotals, issues, tools, preventions} = score;
|
|
78
|
+
// For each test act:
|
|
79
|
+
testActs.forEach(act => {
|
|
80
|
+
// If a standard result with valid totals and instances exists:
|
|
81
|
+
const {which, standardResult} = act;
|
|
82
|
+
if (
|
|
83
|
+
standardResult
|
|
84
|
+
&& standardResult.totals
|
|
85
|
+
&& standardResult.totals.length === 4
|
|
86
|
+
&& standardResult.instances
|
|
87
|
+
&& standardResult.instances.length
|
|
88
|
+
) {
|
|
89
|
+
// Add the tool totals to the score.
|
|
90
|
+
const {totals} = standardResult;
|
|
91
|
+
tools[which] = totals;
|
|
92
|
+
toolTotals.forEach((total, index) => {
|
|
93
|
+
toolTotals[index] += toolWeight * totals[index];
|
|
94
|
+
summary.tools += toolWeight * totals[index] * severityWeights[index];
|
|
95
|
+
});
|
|
96
|
+
// Update the issue totals for the tool.
|
|
97
|
+
const issueTotals = {};
|
|
98
|
+
standardResult.instances.forEach(instance => {
|
|
99
|
+
const issueID = issueIndex[which][instance.issueID];
|
|
100
|
+
let indexIssueID;
|
|
101
|
+
if (issueID) {
|
|
102
|
+
indexIssueID = issueID;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
indexIssueID = issueMatcher.find(pattern => {
|
|
106
|
+
const patternRE = new RegExp(pattern);
|
|
107
|
+
return patternRE.test(instance.issueID);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (indexIssueID) {
|
|
111
|
+
if (! issueTotals[indexIssueID]) {
|
|
112
|
+
issueTotals[indexIssueID] = 0;
|
|
113
|
+
}
|
|
114
|
+
issueTotals[indexIssueID] += instance.count || 1;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`ERROR: ${instance.issueID} not found in issueClasses`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Update the issue totals in the score.
|
|
121
|
+
Object.keys(issueTotals).forEach(id => {
|
|
122
|
+
issues[id] = Math.max(issues[id] || 0, issueTotals[id]);
|
|
123
|
+
});
|
|
124
|
+
summary.issues = Object.values(issues).reduce((total, current) => total + current);
|
|
125
|
+
}
|
|
126
|
+
// Otherwise, i.e. if no result with totals exists:
|
|
127
|
+
else {
|
|
128
|
+
// Add a prevented result to the act if not already there.
|
|
129
|
+
if (! act.result) {
|
|
130
|
+
act.result = {};
|
|
131
|
+
}
|
|
132
|
+
if (! act.result.prevented) {
|
|
133
|
+
act.result.prevented = true;
|
|
134
|
+
};
|
|
135
|
+
// Add the tool and the prevention score to the score.
|
|
136
|
+
score.preventions[which] = preventionWeight;
|
|
137
|
+
summary.preventions += preventionWeight;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
// Add the log score to the score.
|
|
141
|
+
const {jobData} = report;
|
|
142
|
+
summary.log = Math.max(0, Math.round(
|
|
143
|
+
logWeights.logCount * jobData.logCount
|
|
144
|
+
+ logWeights.logSize * jobData.logSize +
|
|
145
|
+
+ logWeights.errorLogCount * jobData.errorLogCount
|
|
146
|
+
+ logWeights.errorLogSize * jobData.errorLogSize
|
|
147
|
+
+ logWeights.prohibitedCount * jobData.prohibitedCount +
|
|
148
|
+
+ logWeights.visitRejectionCount * jobData.visitRejectionCount
|
|
149
|
+
));
|
|
150
|
+
summary.latency = Math.round(
|
|
151
|
+
latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
|
|
152
|
+
);
|
|
153
|
+
// Round the scores.
|
|
154
|
+
Object.keys(summary).forEach(summaryTypeName => {
|
|
155
|
+
summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
|
|
156
|
+
});
|
|
157
|
+
toolTotals.forEach((severityTotal, index) => {
|
|
158
|
+
toolTotals[index] = Math.round(toolTotals[index]);
|
|
159
|
+
});
|
|
160
|
+
// Add the total score to the score.
|
|
161
|
+
summary.total = summary.issues
|
|
162
|
+
+ summary.tools
|
|
163
|
+
+ summary.preventions
|
|
164
|
+
+ summary.log
|
|
165
|
+
+ summary.latency;
|
|
166
|
+
// Add the score to the report.
|
|
167
|
+
report.score = score;
|
|
168
|
+
}
|
|
169
|
+
// Otherwise, i.e. if none of them is a test act:
|
|
170
|
+
else {
|
|
171
|
+
// Report this.
|
|
172
|
+
console.log('ERROR: No test acts');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Otherwise, i.e. if there are no acts in the report:
|
|
176
|
+
else {
|
|
177
|
+
// Report this.
|
|
178
|
+
console.log('ERROR: No acts');
|
|
179
|
+
}
|
|
180
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "ts26",
|
|
3
|
+
"what": "tests of 10 tools",
|
|
4
|
+
"strict": true,
|
|
5
|
+
"timeLimit": 600,
|
|
6
|
+
"acts": [
|
|
7
|
+
{
|
|
8
|
+
"type": "placeholder",
|
|
9
|
+
"which": "main",
|
|
10
|
+
"launch": "webkit"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "tenonRequest",
|
|
14
|
+
"id": "a",
|
|
15
|
+
"withNewContent": true,
|
|
16
|
+
"what": "Tenon API version 2 test request, with URL"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "test",
|
|
20
|
+
"which": "testaro",
|
|
21
|
+
"what": "Testaro motion test requiring webkit",
|
|
22
|
+
"withItems": true,
|
|
23
|
+
"rules": ["y", "motion"],
|
|
24
|
+
"args": {
|
|
25
|
+
"motion": [2500, 2500, 5]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "placeholder",
|
|
30
|
+
"which": "main",
|
|
31
|
+
"launch": "chromium"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "test",
|
|
35
|
+
"which": "testaro",
|
|
36
|
+
"what": "Testaro rules except motion",
|
|
37
|
+
"withItems": true,
|
|
38
|
+
"rules": ["n", "motion"],
|
|
39
|
+
"args": {
|
|
40
|
+
"focInd": [false, 250],
|
|
41
|
+
"hover": [20]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"type": "test",
|
|
46
|
+
"which": "alfa",
|
|
47
|
+
"what": "Siteimprove alfa"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "test",
|
|
51
|
+
"which": "axe",
|
|
52
|
+
"detailLevel": 2,
|
|
53
|
+
"rules": [],
|
|
54
|
+
"what": "Axe core, all rules"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "test",
|
|
58
|
+
"which": "continuum",
|
|
59
|
+
"what": "Continuum Community Edition"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "test",
|
|
63
|
+
"which": "htmlcs",
|
|
64
|
+
"what": "HTML CodeSniffer"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "test",
|
|
68
|
+
"which": "ibm",
|
|
69
|
+
"withNewContent": false,
|
|
70
|
+
"withItems": true,
|
|
71
|
+
"what": "IBM Accessibility Checker"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "test",
|
|
75
|
+
"which": "nuVal",
|
|
76
|
+
"what": "Nu Html Checker"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "test",
|
|
80
|
+
"which": "qualWeb",
|
|
81
|
+
"withNewContent": false,
|
|
82
|
+
"what": "QualWeb"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "test",
|
|
86
|
+
"which": "wave",
|
|
87
|
+
"reportType": 4,
|
|
88
|
+
"what": "WAVE, report type 4"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "test",
|
|
92
|
+
"which": "tenon",
|
|
93
|
+
"id": "a",
|
|
94
|
+
"what": "Tenon API version 2 result retrieval"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
package/procs/score/tsp24.js
DELETED
|
@@ -1,740 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
tsp24
|
|
3
|
-
Testilo score proc 24
|
|
4
|
-
|
|
5
|
-
Computes scores from Testilo script ts23 or ts24 and adds them to a report.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// IMPORTS
|
|
9
|
-
|
|
10
|
-
const {issues} = require('./tic24');
|
|
11
|
-
|
|
12
|
-
// CONSTANTS
|
|
13
|
-
|
|
14
|
-
// ID of this proc.
|
|
15
|
-
const scoreProcID = 'tsp24';
|
|
16
|
-
// Configuration disclosures.
|
|
17
|
-
const logWeights = {
|
|
18
|
-
logCount: 0.5,
|
|
19
|
-
logSize: 0.01,
|
|
20
|
-
errorLogCount: 1,
|
|
21
|
-
errorLogSize: 0.02,
|
|
22
|
-
prohibitedCount: 15,
|
|
23
|
-
visitTimeoutCount: 10,
|
|
24
|
-
visitRejectionCount: 10,
|
|
25
|
-
visitLatency: 1
|
|
26
|
-
};
|
|
27
|
-
// Normal latency (1 second per visit).
|
|
28
|
-
const normalLatency = 13;
|
|
29
|
-
// How much each unclassified failure adds to the score.
|
|
30
|
-
const soloWeight = 2;
|
|
31
|
-
// How much classified issues add to the score.
|
|
32
|
-
const issueWeights = {
|
|
33
|
-
// Added per issue.
|
|
34
|
-
absolute: 2,
|
|
35
|
-
// Added per instance reported by the tool with the largest count.
|
|
36
|
-
largest: 1,
|
|
37
|
-
// Added per instance reported by each other tool.
|
|
38
|
-
smaller: 0.4
|
|
39
|
-
};
|
|
40
|
-
// How much each prevention adds to the score.
|
|
41
|
-
const preventionWeights = {
|
|
42
|
-
testaro: 50,
|
|
43
|
-
other: 100
|
|
44
|
-
};
|
|
45
|
-
// Non-Testaro tools.
|
|
46
|
-
const otherTools = [
|
|
47
|
-
'alfa',
|
|
48
|
-
'axe',
|
|
49
|
-
'continuum',
|
|
50
|
-
'htmlcs',
|
|
51
|
-
'ibm',
|
|
52
|
-
'nuVal',
|
|
53
|
-
'qualWeb',
|
|
54
|
-
'tenon',
|
|
55
|
-
'wave'
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
// VARIABLES
|
|
59
|
-
|
|
60
|
-
let toolDetails = {};
|
|
61
|
-
let issueDetails = {};
|
|
62
|
-
let summary = {};
|
|
63
|
-
let preventionScores = {};
|
|
64
|
-
|
|
65
|
-
// FUNCTIONS
|
|
66
|
-
|
|
67
|
-
// Initialize the variables.
|
|
68
|
-
const init = () => {
|
|
69
|
-
toolDetails = {};
|
|
70
|
-
issueDetails = {
|
|
71
|
-
issues: {},
|
|
72
|
-
solos: {}
|
|
73
|
-
};
|
|
74
|
-
summary = {
|
|
75
|
-
total: 0,
|
|
76
|
-
log: 0,
|
|
77
|
-
preventions: 0,
|
|
78
|
-
solos: 0,
|
|
79
|
-
issues: []
|
|
80
|
-
};
|
|
81
|
-
preventionScores = {};
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Adds a score to the tool details.
|
|
85
|
-
const addDetail = (actWhich, testID, addition = 1) => {
|
|
86
|
-
if (addition) {
|
|
87
|
-
if (!toolDetails[actWhich]) {
|
|
88
|
-
toolDetails[actWhich] = {};
|
|
89
|
-
}
|
|
90
|
-
if (!toolDetails[actWhich][testID]) {
|
|
91
|
-
toolDetails[actWhich][testID] = 0;
|
|
92
|
-
}
|
|
93
|
-
toolDetails[actWhich][testID] += Math.round(addition);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
// Scores a report.
|
|
97
|
-
exports.scorer = async report => {
|
|
98
|
-
// Initialize the variables.
|
|
99
|
-
init();
|
|
100
|
-
// If there are any acts in the report:
|
|
101
|
-
const {acts} = report;
|
|
102
|
-
if (Array.isArray(acts)) {
|
|
103
|
-
// If any of them are test acts:
|
|
104
|
-
const testActs = acts.filter(act => act.type === 'test');
|
|
105
|
-
if (testActs.length) {
|
|
106
|
-
// For each test act:
|
|
107
|
-
testActs.forEach(test => {
|
|
108
|
-
// Add a prevented result if no result exists.
|
|
109
|
-
if (! test.result) {
|
|
110
|
-
test.result = {
|
|
111
|
-
prevented: true
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
const {which} = test;
|
|
115
|
-
// Add scores to the tool details.
|
|
116
|
-
if (which === 'alfa') {
|
|
117
|
-
const issues = test.result && test.result.items;
|
|
118
|
-
if (issues && Array.isArray(issues)) {
|
|
119
|
-
issues.forEach(issue => {
|
|
120
|
-
const {verdict, rule} = issue;
|
|
121
|
-
if (verdict && rule) {
|
|
122
|
-
const {ruleID} = rule;
|
|
123
|
-
if (ruleID) {
|
|
124
|
-
// Add 4 per failure, 1 per warning (cantTell).
|
|
125
|
-
addDetail(which, ruleID, verdict === 'failed' ? 4 : 1);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else if (which === 'axe') {
|
|
132
|
-
const impactScores = {
|
|
133
|
-
minor: 1,
|
|
134
|
-
moderate: 2,
|
|
135
|
-
serious: 3,
|
|
136
|
-
critical: 4
|
|
137
|
-
};
|
|
138
|
-
const tests = test.result && test.result.details;
|
|
139
|
-
if (tests) {
|
|
140
|
-
const warnings = tests.incomplete;
|
|
141
|
-
const {violations} = tests;
|
|
142
|
-
[[warnings, 0.25], [violations, 1]].forEach(issueSeverity => {
|
|
143
|
-
if (issueSeverity[0] && Array.isArray(issueSeverity[0])) {
|
|
144
|
-
issueSeverity[0].forEach(issueType => {
|
|
145
|
-
const {id, nodes} = issueType;
|
|
146
|
-
if (id && nodes && Array.isArray(nodes)) {
|
|
147
|
-
nodes.forEach(node => {
|
|
148
|
-
const {impact} = node;
|
|
149
|
-
if (impact) {
|
|
150
|
-
// Add the impact score for a violation or 25% of it for a warning.
|
|
151
|
-
addDetail(which, id, issueSeverity[1] * impactScores[impact]);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else if (which === 'continuum') {
|
|
161
|
-
const issues = test.result;
|
|
162
|
-
if (issues && Array.isArray(issues)) {
|
|
163
|
-
issues.forEach(issue => {
|
|
164
|
-
// Add 4 per violation.
|
|
165
|
-
addDetail(which, issue.engineTestId, 4);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (which === 'htmlcs') {
|
|
170
|
-
const issues = test.result;
|
|
171
|
-
if (issues) {
|
|
172
|
-
['Error', 'Warning'].forEach(issueSeverityName => {
|
|
173
|
-
const severityData = issues[issueSeverityName];
|
|
174
|
-
if (severityData) {
|
|
175
|
-
const issueTypes = Object.keys(severityData);
|
|
176
|
-
issueTypes.forEach(issueTypeName => {
|
|
177
|
-
const issueArrays = Object.values(severityData[issueTypeName]);
|
|
178
|
-
const issueCount = issueArrays.reduce((count, array) => count + array.length, 0);
|
|
179
|
-
const severityCode = issueSeverityName[0].toLowerCase();
|
|
180
|
-
const code = `${severityCode}:${issueTypeName}`;
|
|
181
|
-
// Add 4 per error, 1 per warning.
|
|
182
|
-
const weight = severityCode === 'e' ? 4 : 1;
|
|
183
|
-
addDetail(which, code, weight * issueCount);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else if (which === 'ibm') {
|
|
190
|
-
const {result} = test;
|
|
191
|
-
const {content, url} = result;
|
|
192
|
-
if (content && url) {
|
|
193
|
-
let preferredMode = 'content';
|
|
194
|
-
if (
|
|
195
|
-
content.error ||
|
|
196
|
-
(content.totals &&
|
|
197
|
-
content.totals.violation &&
|
|
198
|
-
url.totals &&
|
|
199
|
-
url.totals.violation &&
|
|
200
|
-
url.totals.violation > content.totals.violation)
|
|
201
|
-
) {
|
|
202
|
-
preferredMode = 'url';
|
|
203
|
-
}
|
|
204
|
-
const {items} = result[preferredMode];
|
|
205
|
-
if (items && Array.isArray(items)) {
|
|
206
|
-
items.forEach(issue => {
|
|
207
|
-
const {ruleId, level} = issue;
|
|
208
|
-
if (ruleId && level) {
|
|
209
|
-
// Add 4 per violation, 1 per warning (recommendation).
|
|
210
|
-
addDetail(which, ruleId, level === 'violation' ? 4 : 1);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else if (which === 'nuVal') {
|
|
217
|
-
const issues = test.result && test.result.messages;
|
|
218
|
-
if (issues) {
|
|
219
|
-
issues.forEach(issue => {
|
|
220
|
-
// Add 4 per error, 1 per warning.
|
|
221
|
-
const weight = issue.type === 'error' ? 4 : 1;
|
|
222
|
-
addDetail(which, issue.message, weight);
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else if (which === 'qualWeb') {
|
|
227
|
-
// For each section of the results:
|
|
228
|
-
const modules = test.result && test.result.modules;
|
|
229
|
-
if (modules) {
|
|
230
|
-
['act-rules', 'wcag-techniques', 'best-practices'].forEach(type => {
|
|
231
|
-
// For each test in the section:
|
|
232
|
-
const {assertions} = modules[type];
|
|
233
|
-
if (assertions) {
|
|
234
|
-
const issueIDs = Object.keys(assertions);
|
|
235
|
-
issueIDs.forEach(issueID => {
|
|
236
|
-
// For each error or warning from the test:
|
|
237
|
-
const {results} = assertions[issueID];
|
|
238
|
-
results.forEach(result => {
|
|
239
|
-
// Add 4 per error, 1 per warning, per element.
|
|
240
|
-
let weight = 0;
|
|
241
|
-
const {verdict} = result;
|
|
242
|
-
if (verdict === 'error') {
|
|
243
|
-
weight = 4;
|
|
244
|
-
}
|
|
245
|
-
else if (verdict === 'warning') {
|
|
246
|
-
weight = 1;
|
|
247
|
-
}
|
|
248
|
-
if (weight) {
|
|
249
|
-
addDetail(which, issueID, result.elements.length * weight);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
else if (which === 'tenon') {
|
|
258
|
-
const issues =
|
|
259
|
-
test.result && test.result.data && test.result.data.resultSet;
|
|
260
|
-
if (issues && Array.isArray(issues)) {
|
|
261
|
-
issues.forEach(issue => {
|
|
262
|
-
const {tID, priority, certainty} = issue;
|
|
263
|
-
if (tID && priority && certainty) {
|
|
264
|
-
// Add 4 per issue if certainty and priority 100, less if less.
|
|
265
|
-
addDetail(which, tID, certainty * priority / 2500);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
else if (which === 'wave') {
|
|
271
|
-
const severityScores = {
|
|
272
|
-
error: 4,
|
|
273
|
-
contrast: 3,
|
|
274
|
-
alert: 1
|
|
275
|
-
};
|
|
276
|
-
const issueSeverities = test.result && test.result.categories;
|
|
277
|
-
if (issueSeverities) {
|
|
278
|
-
['error', 'contrast', 'alert'].forEach(issueSeverity => {
|
|
279
|
-
const {items} = issueSeverities[issueSeverity];
|
|
280
|
-
if (items) {
|
|
281
|
-
const testIDs = Object.keys(items);
|
|
282
|
-
if (testIDs.length) {
|
|
283
|
-
testIDs.forEach(testID => {
|
|
284
|
-
const {count} = items[testID];
|
|
285
|
-
if (count) {
|
|
286
|
-
// Add 4 per error, 3 per contrast error, 1 per warning (alert).
|
|
287
|
-
addDetail(
|
|
288
|
-
which, `${issueSeverity[0]}:${testID}`, count * severityScores[issueSeverity]
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
else if (which === 'allHidden') {
|
|
298
|
-
const {result} = test;
|
|
299
|
-
if (
|
|
300
|
-
result
|
|
301
|
-
&& ['hidden', 'reallyHidden', 'visHidden', 'ariaHidden'].every(
|
|
302
|
-
key => result[key]
|
|
303
|
-
&& ['document', 'body', 'main'].every(
|
|
304
|
-
element => typeof result[key][element] === 'boolean'
|
|
305
|
-
)
|
|
306
|
-
)
|
|
307
|
-
) {
|
|
308
|
-
// Get a score for the test.
|
|
309
|
-
const score = 8 * result.hidden.document
|
|
310
|
-
+ 8 * result.hidden.body
|
|
311
|
-
+ 6 * result.hidden.main
|
|
312
|
-
+ 10 * result.reallyHidden.document
|
|
313
|
-
+ 10 * result.reallyHidden.body
|
|
314
|
-
+ 8 * result.reallyHidden.main
|
|
315
|
-
+ 8 * result.visHidden.document
|
|
316
|
-
+ 8 * result.visHidden.body
|
|
317
|
-
+ 6 * result.visHidden.main
|
|
318
|
-
+ 10 * result.ariaHidden.document
|
|
319
|
-
+ 10 * result.ariaHidden.body
|
|
320
|
-
+ 8 * result.ariaHidden.main;
|
|
321
|
-
// Add the score.
|
|
322
|
-
addDetail('testaro', which, score);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
else if (which === 'autocomplete') {
|
|
326
|
-
const count = test.result && test.result.total;
|
|
327
|
-
if (typeof count === 'number') {
|
|
328
|
-
// Add 4 per autocomplete violation.
|
|
329
|
-
addDetail('testaro', which, 4 * count);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
else if (which === 'bulk') {
|
|
333
|
-
const count = test.result && test.result.visibleElements;
|
|
334
|
-
if (typeof count === 'number') {
|
|
335
|
-
// Add 1 per 300 visible elements beyond 300.
|
|
336
|
-
addDetail('testaro', which, Math.max(0, count / 300 - 1));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
else if (which === 'docType') {
|
|
340
|
-
// If document has no or invalid doctype:
|
|
341
|
-
const hasType = test.result && test.result.docHasType;
|
|
342
|
-
if (typeof hasType === 'boolean' && ! hasType) {
|
|
343
|
-
// Add 10.
|
|
344
|
-
addDetail('testaro', which, 10);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
else if (which === 'dupAtt') {
|
|
348
|
-
const count = test.result && test.result.total;
|
|
349
|
-
if (typeof count === 'number') {
|
|
350
|
-
// Add 2 per element with duplicate attributes.
|
|
351
|
-
addDetail('testaro', which, 2 * count);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
else if (which === 'embAc') {
|
|
355
|
-
const issueCounts = test.result && test.result.totals;
|
|
356
|
-
if (issueCounts) {
|
|
357
|
-
const counts = Object.values(issueCounts);
|
|
358
|
-
const total = counts.reduce((sum, current) => sum + current);
|
|
359
|
-
// Add 3 per embedded element.
|
|
360
|
-
addDetail('testaro', which, 3 * total);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
else if (which === 'filter') {
|
|
364
|
-
const totals = test.result && test.result.totals;
|
|
365
|
-
if (totals) {
|
|
366
|
-
// Add 2 per filter-styled element, 1 per filter-impacted element.
|
|
367
|
-
addDetail('testaro', which, 2 * totals.elements + totals.impact);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
else if (which === 'focAll') {
|
|
371
|
-
const discrepancy = test.result && test.result.discrepancy;
|
|
372
|
-
if (discrepancy) {
|
|
373
|
-
// Add 2 per discrepancy.
|
|
374
|
-
addDetail('testaro', which, 2 * Math.abs(discrepancy));
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else if (which === 'focInd') {
|
|
378
|
-
const issueTypes =
|
|
379
|
-
test.result && test.result.totals && test.result.totals.types;
|
|
380
|
-
if (issueTypes) {
|
|
381
|
-
const missingCount = issueTypes.indicatorMissing
|
|
382
|
-
&& issueTypes.indicatorMissing.total
|
|
383
|
-
|| 0;
|
|
384
|
-
const badCount = issueTypes.nonOutlinePresent
|
|
385
|
-
&& issueTypes.nonOutlinePresent.total
|
|
386
|
-
|| 0;
|
|
387
|
-
// Add 3 per missing, 1 per non-outline focus indicator.
|
|
388
|
-
addDetail('testaro', which, badCount + 3 * missingCount);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
else if (which === 'focOp') {
|
|
392
|
-
const issueTypes =
|
|
393
|
-
test.result && test.result.totals && test.result.totals.types;
|
|
394
|
-
if (issueTypes) {
|
|
395
|
-
const noOpCount = issueTypes.onlyFocusable && issueTypes.onlyFocusable.total || 0;
|
|
396
|
-
const noFocCount = issueTypes.onlyOperable && issueTypes.onlyOperable.total || 0;
|
|
397
|
-
// Add 2 per unfocusable, 0.5 per inoperable element.
|
|
398
|
-
addDetail('testaro', which, 2 * noFocCount + 0.5 * noOpCount);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
else if (which === 'focVis') {
|
|
402
|
-
const count = test.result && test.result.total;
|
|
403
|
-
if (count) {
|
|
404
|
-
// Add 1 per link outside the viewport.
|
|
405
|
-
addDetail('testaro', which, count);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
else if (which === 'hover') {
|
|
409
|
-
const issues = test.result && test.result.totals;
|
|
410
|
-
if (issues) {
|
|
411
|
-
const {
|
|
412
|
-
impactTriggers,
|
|
413
|
-
additions,
|
|
414
|
-
removals,
|
|
415
|
-
opacityChanges,
|
|
416
|
-
opacityImpact,
|
|
417
|
-
unhoverables,
|
|
418
|
-
noCursors,
|
|
419
|
-
badCursors,
|
|
420
|
-
noIndicators,
|
|
421
|
-
badIndicators
|
|
422
|
-
} = issues;
|
|
423
|
-
// Add score with weights on hover-impact types.
|
|
424
|
-
const score = 2 * impactTriggers
|
|
425
|
-
+ 0.3 * additions
|
|
426
|
-
+ removals
|
|
427
|
-
+ 0.2 * opacityChanges
|
|
428
|
-
+ 0.1 * opacityImpact
|
|
429
|
-
+ unhoverables
|
|
430
|
-
+ 3 * noCursors
|
|
431
|
-
+ 2 * badCursors
|
|
432
|
-
+ noIndicators
|
|
433
|
-
+ badIndicators;
|
|
434
|
-
if (score) {
|
|
435
|
-
addDetail('testaro', which, score);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
else if (which === 'labClash') {
|
|
440
|
-
const mislabeledCount = test.result
|
|
441
|
-
&& test.result.totals
|
|
442
|
-
&& test.result.totals.mislabeled
|
|
443
|
-
|| 0;
|
|
444
|
-
// Add 1 per element with conflicting labels (ignoring unlabeled elements).
|
|
445
|
-
addDetail('testaro', which, mislabeledCount);
|
|
446
|
-
}
|
|
447
|
-
else if (which === 'linkTo') {
|
|
448
|
-
const count = test.result && test.result.total;
|
|
449
|
-
if (count) {
|
|
450
|
-
// Add 2 per link with no destination.
|
|
451
|
-
addDetail('testaro', which, count);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
else if (which === 'linkUl') {
|
|
455
|
-
const totals = test.result && test.result.totals && test.result.totals.adjacent;
|
|
456
|
-
if (totals) {
|
|
457
|
-
const nonUl = totals.total - totals.underlined || 0;
|
|
458
|
-
// Add 2 per non-underlined adjacent link.
|
|
459
|
-
addDetail('testaro', which, 2 * nonUl);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
else if (which === 'menuNav') {
|
|
463
|
-
const issueCount = test.result
|
|
464
|
-
&& test.result.totals
|
|
465
|
-
&& test.result.totals.navigations
|
|
466
|
-
&& test.result.totals.navigations.all
|
|
467
|
-
&& test.result.totals.navigations.all.incorrect
|
|
468
|
-
|| 0;
|
|
469
|
-
// Add 2 per defect.
|
|
470
|
-
addDetail('testaro', which, 2 * issueCount);
|
|
471
|
-
}
|
|
472
|
-
else if (which === 'miniText') {
|
|
473
|
-
const items = test.result && test.result.items;
|
|
474
|
-
if (items && items.length) {
|
|
475
|
-
// Add 1 per 100 characters of small-text.
|
|
476
|
-
const totalLength = items.reduce((total, item) => total + item.length, 0);
|
|
477
|
-
addDetail('testaro', which, Math.floor(totalLength / 100));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
else if (which === 'motion') {
|
|
481
|
-
const data = test.result;
|
|
482
|
-
if (data) {
|
|
483
|
-
const {
|
|
484
|
-
meanLocalRatio,
|
|
485
|
-
maxLocalRatio,
|
|
486
|
-
globalRatio,
|
|
487
|
-
meanPixelChange,
|
|
488
|
-
maxPixelChange,
|
|
489
|
-
changeFrequency
|
|
490
|
-
} = data;
|
|
491
|
-
const score = 2 * (meanLocalRatio - 1)
|
|
492
|
-
+ maxLocalRatio - 1
|
|
493
|
-
+ globalRatio - 1
|
|
494
|
-
+ meanPixelChange / 10000
|
|
495
|
-
+ maxPixelChange / 25000
|
|
496
|
-
+ 3 * changeFrequency
|
|
497
|
-
|| 0;
|
|
498
|
-
addDetail('testaro', which, score);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
else if (which === 'nonTable') {
|
|
502
|
-
const total = test.result && test.result.total;
|
|
503
|
-
if (total) {
|
|
504
|
-
// Add 2 per pseudotable.
|
|
505
|
-
addDetail('testaro', which, 2 * total);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
else if (which === 'radioSet') {
|
|
509
|
-
const totals = test.result && test.result.totals;
|
|
510
|
-
if (totals) {
|
|
511
|
-
const {total, inSet} = totals;
|
|
512
|
-
const score = total - inSet || 0;
|
|
513
|
-
// Add 1 per misissueed radio button.
|
|
514
|
-
addDetail('testaro', which, score);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
else if (which === 'role') {
|
|
518
|
-
const badCount = test.result && test.result.badRoleElements || 0;
|
|
519
|
-
const redundantCount = test.result && test.result.redundantRoleElements || 0;
|
|
520
|
-
// Add 2 per bad role and 1 per redundant role.
|
|
521
|
-
addDetail('testaro', which, 2 * badCount + redundantCount);
|
|
522
|
-
}
|
|
523
|
-
else if (which === 'styleDiff') {
|
|
524
|
-
const totals = test.result && test.result.totals;
|
|
525
|
-
if (totals) {
|
|
526
|
-
let score = 0;
|
|
527
|
-
// For each element type that has any style diversity:
|
|
528
|
-
Object.values(totals).forEach(typeData => {
|
|
529
|
-
const {total, subtotals} = typeData;
|
|
530
|
-
if (subtotals) {
|
|
531
|
-
const styleCount = subtotals.length;
|
|
532
|
-
const plurality = subtotals[0];
|
|
533
|
-
const minorities = total - plurality;
|
|
534
|
-
// Add 1 per style, 0.2 per element with any nonplurality style.
|
|
535
|
-
score += styleCount + 0.2 * minorities;
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
addDetail('testaro', which, score);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
else if (which === 'tabNav') {
|
|
542
|
-
const issueCount = test.result
|
|
543
|
-
&& test.result.totals
|
|
544
|
-
&& test.result.totals.navigations
|
|
545
|
-
&& test.result.totals.navigations.all
|
|
546
|
-
&& test.result.totals.navigations.all.incorrect
|
|
547
|
-
|| 0;
|
|
548
|
-
// Add 2 per defect.
|
|
549
|
-
addDetail('testaro', which, 2 * issueCount);
|
|
550
|
-
}
|
|
551
|
-
else if (which === 'titledEl') {
|
|
552
|
-
const total = test.result && test.result.total;
|
|
553
|
-
if (total) {
|
|
554
|
-
const score = 4 * total;
|
|
555
|
-
// Add 4 per mistitled element.
|
|
556
|
-
addDetail('testaro', which, score);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
else if (which === 'zIndex') {
|
|
560
|
-
const issueCount = test.result && test.result.totals && test.result.totals.total || 0;
|
|
561
|
-
// Add 1 per non-auto zIndex.
|
|
562
|
-
addDetail('testaro', which, issueCount);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
// Get the prevention scores and add them to the summary.
|
|
566
|
-
const actsPrevented = testActs.filter(test => test.result.prevented);
|
|
567
|
-
actsPrevented.forEach(act => {
|
|
568
|
-
if (otherTools.includes(act.which)) {
|
|
569
|
-
preventionScores[act.which] = preventionWeights.other;
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
preventionScores[`testaro-${act.which}`] = preventionWeights.testaro;
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
const preventionScore = Object.values(preventionScores).reduce(
|
|
576
|
-
(sum, current) => sum + current,
|
|
577
|
-
0
|
|
578
|
-
);
|
|
579
|
-
const roundedPreventionScore = Math.round(preventionScore);
|
|
580
|
-
summary.preventions = roundedPreventionScore;
|
|
581
|
-
summary.total += roundedPreventionScore;
|
|
582
|
-
// Initialize a table of the issues to which tests belong.
|
|
583
|
-
const toolIssues = {
|
|
584
|
-
testaro: {},
|
|
585
|
-
alfa: {},
|
|
586
|
-
axe: {},
|
|
587
|
-
continuum: {},
|
|
588
|
-
htmlcs: {},
|
|
589
|
-
ibm: {},
|
|
590
|
-
nuVal: {},
|
|
591
|
-
qualWeb: {},
|
|
592
|
-
tenon: {},
|
|
593
|
-
wave: {}
|
|
594
|
-
};
|
|
595
|
-
// Initialize a table of the regular expressions of variably named tests of tools.
|
|
596
|
-
const testMatchers = {};
|
|
597
|
-
Object.keys(issues).forEach(issueName => {
|
|
598
|
-
Object.keys(issues[issueName].tools).forEach(toolName => {
|
|
599
|
-
Object.keys(issues[issueName].tools[toolName]).forEach(testID => {
|
|
600
|
-
// Update the issue table.
|
|
601
|
-
toolIssues[toolName][testID] = issueName;
|
|
602
|
-
// If the test is variably named:
|
|
603
|
-
if (issues[issueName].tools[toolName][testID].variable) {
|
|
604
|
-
// Add its regular expression, as multiline, to the variably-named-test table.
|
|
605
|
-
if (! testMatchers[toolName]) {
|
|
606
|
-
testMatchers[toolName] = [];
|
|
607
|
-
}
|
|
608
|
-
testMatchers[toolName].push(new RegExp(testID, 's'));
|
|
609
|
-
}
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
// For each tool with any scores:
|
|
614
|
-
Object.keys(toolDetails).forEach(toolName => {
|
|
615
|
-
const matchers = testMatchers[toolName];
|
|
616
|
-
// For each test with any scores in the tool:
|
|
617
|
-
Object.keys(toolDetails[toolName]).forEach(testMessage => {
|
|
618
|
-
// Initialize the test ID as the reported test message.
|
|
619
|
-
let testID = testMessage;
|
|
620
|
-
// Get the issue of the test, if it has a fixed name and is in a issue.
|
|
621
|
-
let issueName = toolIssues[toolName][testMessage];
|
|
622
|
-
// If the test has a variable name or is a solo test:
|
|
623
|
-
if (! issueName) {
|
|
624
|
-
// Determine whether the tool has variably named tests and the test is among them.
|
|
625
|
-
testRegExp = matchers && matchers.find(matcher => matcher.test(testMessage));
|
|
626
|
-
// If so:
|
|
627
|
-
if (testRegExp) {
|
|
628
|
-
// Make the matching regular expression the test ID.
|
|
629
|
-
testID = testRegExp.source;
|
|
630
|
-
// Get the issue of the test.
|
|
631
|
-
issueName = toolIssues[toolName][testID];
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
// If the test is in a issue:
|
|
635
|
-
if (issueName) {
|
|
636
|
-
// Initialize its score as its score in the tool details.
|
|
637
|
-
if (! issueDetails.issues[issueName]) {
|
|
638
|
-
issueDetails.issues[issueName] = {
|
|
639
|
-
wcag: issues[issueName].wcag,
|
|
640
|
-
tools: {}
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
if (! issueDetails.issues[issueName].tools[toolName]) {
|
|
644
|
-
issueDetails.issues[issueName].tools[toolName] = {};
|
|
645
|
-
}
|
|
646
|
-
let weightedScore = toolDetails[toolName][testMessage];
|
|
647
|
-
// Weight that by the issue weight and normalize it to a 1–4 scale per instance.
|
|
648
|
-
weightedScore *= issues[issueName].weight / 4;
|
|
649
|
-
// Adjust the score for the quality of the test.
|
|
650
|
-
weightedScore *= issues[issueName].tools[toolName][testID].quality;
|
|
651
|
-
// Round the score, but not to less than 1.
|
|
652
|
-
const roundedScore = Math.max(Math.round(weightedScore), 1);
|
|
653
|
-
// Add the rounded score and the test description to the issue details.
|
|
654
|
-
issueDetails.issues[issueName].tools[toolName][testID] = {
|
|
655
|
-
score: roundedScore,
|
|
656
|
-
what: issues[issueName].tools[toolName][testID].what
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
// Otherwise, i.e. if the test is solo:
|
|
660
|
-
else {
|
|
661
|
-
if (! issueDetails.solos[toolName]) {
|
|
662
|
-
issueDetails.solos[toolName] = {};
|
|
663
|
-
}
|
|
664
|
-
const roundedScore = Math.round(toolDetails[toolName][testID]);
|
|
665
|
-
issueDetails.solos[toolName][testID] = roundedScore;
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
// Determine the issue scores and add them to the summary.
|
|
670
|
-
const issueNames = Object.keys(issueDetails.issues);
|
|
671
|
-
const {absolute, largest, smaller} = issueWeights;
|
|
672
|
-
// For each issue with any scores:
|
|
673
|
-
issueNames.forEach(issueName => {
|
|
674
|
-
const scores = [];
|
|
675
|
-
// For each tool with any scores in the issue:
|
|
676
|
-
const issueToolData = Object.values(issueDetails.issues[issueName].tools);
|
|
677
|
-
issueToolData.forEach(toolObj => {
|
|
678
|
-
// Get the sum of the scores of the tests of the tool in the issue.
|
|
679
|
-
const scoreSum = Object.values(toolObj).reduce(
|
|
680
|
-
(sum, current) => sum + current.score,
|
|
681
|
-
0
|
|
682
|
-
);
|
|
683
|
-
// Add the sum to the list of tool scores in the issue.
|
|
684
|
-
scores.push(scoreSum);
|
|
685
|
-
});
|
|
686
|
-
// Sort the scores in descending order.
|
|
687
|
-
scores.sort((a, b) => b - a);
|
|
688
|
-
// Compute the sum of the absolute score and the weighted largest and other scores.
|
|
689
|
-
const issueScore = absolute
|
|
690
|
-
+ largest * scores[0]
|
|
691
|
-
+ smaller * scores.slice(1).reduce((sum, current) => sum + current, 0);
|
|
692
|
-
const roundedIssueScore = Math.round(issueScore);
|
|
693
|
-
summary.issues.push({
|
|
694
|
-
issueName,
|
|
695
|
-
score: roundedIssueScore
|
|
696
|
-
});
|
|
697
|
-
summary.total += roundedIssueScore;
|
|
698
|
-
});
|
|
699
|
-
summary.issues.sort((a, b) => b.score - a.score);
|
|
700
|
-
// Determine the solo score and add it to the summary.
|
|
701
|
-
const soloToolNames = Object.keys(issueDetails.solos);
|
|
702
|
-
soloToolNames.forEach(toolName => {
|
|
703
|
-
const testIDs = Object.keys(issueDetails.solos[toolName]);
|
|
704
|
-
testIDs.forEach(testID => {
|
|
705
|
-
const score = soloWeight * issueDetails.solos[toolName][testID];
|
|
706
|
-
summary.solos += score;
|
|
707
|
-
summary.total += score;
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
summary.solos = Math.round(summary.solos);
|
|
711
|
-
summary.total = Math.round(summary.total);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
// Get the log score.
|
|
715
|
-
const {jobData} = report;
|
|
716
|
-
const logScore = logWeights.logCount * jobData.logCount
|
|
717
|
-
+ logWeights.logSize * jobData.logSize +
|
|
718
|
-
+ logWeights.errorLogCount * jobData.errorLogCount
|
|
719
|
-
+ logWeights.errorLogSize * jobData.errorLogSize
|
|
720
|
-
+ logWeights.prohibitedCount * jobData.prohibitedCount +
|
|
721
|
-
+ logWeights.visitTimeoutCount * jobData.visitTimeoutCount +
|
|
722
|
-
+ logWeights.visitRejectionCount * jobData.visitRejectionCount
|
|
723
|
-
+ logWeights.visitLatency * (jobData.visitLatency - normalLatency);
|
|
724
|
-
const roundedLogScore = Math.max(0, Math.round(logScore));
|
|
725
|
-
summary.log = roundedLogScore;
|
|
726
|
-
summary.total += roundedLogScore;
|
|
727
|
-
// Add the score facts to the report.
|
|
728
|
-
report.score = {
|
|
729
|
-
scoreProcID,
|
|
730
|
-
logWeights,
|
|
731
|
-
soloWeight,
|
|
732
|
-
issueWeights,
|
|
733
|
-
preventionWeights,
|
|
734
|
-
toolDetails,
|
|
735
|
-
issueDetails,
|
|
736
|
-
preventionScores,
|
|
737
|
-
summary
|
|
738
|
-
};
|
|
739
|
-
};
|
|
740
|
-
exports.issues = issues;
|