testilo 3.2.0 → 3.4.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.
Files changed (41) hide show
  1. package/README.md +33 -3
  2. package/compare.js +8 -8
  3. package/package.json +1 -1
  4. package/procs/compare/cp0/index.html +10 -5
  5. package/procs/compare/cp0/index.js +2 -4
  6. package/procs/compare/cp1/index.html +46 -0
  7. package/procs/compare/cp1/index.js +71 -0
  8. package/procs/digest/dp10a/index.html +3 -3
  9. package/procs/digest/dp10b/index.html +74 -0
  10. package/procs/digest/dp10b/index.js +130 -0
  11. package/procs/digest/dp10c/index.html +55 -0
  12. package/procs/digest/dp10c/index.js +129 -0
  13. package/procs/score/sp10b.js +507 -0
  14. package/procs/score/sp10c.js +465 -0
  15. package/reports/comparative/railsites.html +47 -0
  16. package/reports/comparative/style.css +4 -0
  17. package/reports/digested/35k1r-railpass.html +1130 -844
  18. package/reports/digested/3aee6-eurail.html +36233 -0
  19. package/reports/digested/dp10a/35k1r-railpass.html +9302 -0
  20. package/reports/digested/dp10a/3aee6-eurail.html +35782 -0
  21. package/reports/digested/style.css +2 -2
  22. package/reports/raw/3aee6-eurail.json +35250 -0
  23. package/reports/scored/35k1r-railpass.json +559 -175
  24. package/reports/scored/3aee6-eurail.json +35957 -0
  25. package/score.js +1 -1
  26. package/scoring/data/duplications.json +22 -22
  27. package/scoring/data/groups.json +335 -0
  28. package/scoring/data/packageRules/alfa.tsv +82 -0
  29. package/scoring/data/packageRules/axe.js +390 -0
  30. package/scoring/data/packageRules/ibm.tsv +159 -0
  31. package/scoring/data/packageRules/tenon.tsv +14 -0
  32. package/scoring/data/packageRules/wave.json +1617 -0
  33. package/scoring/data/packageRules/wave.tsv +55 -0
  34. package/scoring/data/testGroups.json +1067 -0
  35. package/scoring/{data → procs}/correlation.js +0 -0
  36. package/scoring/{data → procs}/dupCounts.js +0 -0
  37. package/scoring/procs/groups.js +61 -0
  38. package/scoring/{data → procs}/packageData.js +0 -0
  39. package/scoring/{data → procs}/packageIssues.js +0 -0
  40. package/scoring/procs/regroup.js +39 -0
  41. package/reports/comparative/35k1r-.html +0 -41
@@ -0,0 +1,465 @@
1
+ /*
2
+ sp10c
3
+ Testilo score proc 10c
4
+ Computes scores from a Testaro script that removes API tests from tp10, and adds the scores
5
+ to a report.
6
+ Usage example: node score 35k1r sp10c
7
+ */
8
+
9
+ // ########## IMPORTS
10
+
11
+ // Module to read and write files.
12
+ const fs = require('fs/promises');
13
+
14
+ // CONSTANTS
15
+
16
+ const scoreProcID = 'sp10b';
17
+ // Define the configuration disclosures.
18
+ const logWeights = {
19
+ count: 0.5,
20
+ size: 0.01,
21
+ prohibited: 15,
22
+ visitTimeout: 10,
23
+ visitRejection: 10
24
+ };
25
+ const groupWeights = {
26
+ accessKeyDup: 3,
27
+ activeEmbedding: 2,
28
+ allCaps: 1,
29
+ ariaRefBad: 4,
30
+ autocompleteBad: 2,
31
+ buttonNoText: 4,
32
+ childMissing: 3,
33
+ contrastAA: 3,
34
+ contrastAAA: 1,
35
+ dupID: 2,
36
+ eventKbd: 3,
37
+ fieldSetMissing: 2,
38
+ focusableOperable: 3,
39
+ focusIndication: 3,
40
+ h1Missing: 1,
41
+ headingEmpty: 2,
42
+ headingStruc: 2,
43
+ hoverSurprise: 1,
44
+ htmlLang: 3,
45
+ htmlLangBad: 3,
46
+ iframeNoText: 3,
47
+ imgAltRedundant: 1,
48
+ imgInputNoText: 4,
49
+ imgMapAreaNoText: 3,
50
+ imgNoText: 4,
51
+ inconsistentStyles: 1,
52
+ contrastRisk: 1,
53
+ labelClash: 2,
54
+ labelForBadID: 4,
55
+ langChange: 2,
56
+ leadingClipsText: 3,
57
+ leadingFrozen: 3,
58
+ linkForcesNewWindow: 2,
59
+ linkNoText: 4,
60
+ linkPair: 1,
61
+ linkTextsIdentical: 2,
62
+ linkTitleRedundant: 1,
63
+ linkUnderlines: 2,
64
+ menuNavigation: 2,
65
+ metaBansZoom: 3,
66
+ nameValue: 3,
67
+ noLeading: 2,
68
+ objNoText: 2,
69
+ parentMissing: 3,
70
+ pseudoHeadingRisk: 1,
71
+ pseudoLinkRisk: 2,
72
+ pseudoListRisk: 1,
73
+ roleBad: 3,
74
+ roleBadAttr: 3,
75
+ roleMissingAttr: 3,
76
+ selectFlatRisk: 1,
77
+ selectNoText: 3,
78
+ spontaneousMotion: 2,
79
+ svgImgNoText: 4,
80
+ tabFocusability: 3,
81
+ tabNavigation: 2,
82
+ targetSize: 2,
83
+ textBeyondLandmarks: 1,
84
+ title: 3,
85
+ visibleBulk: 1,
86
+ zIndexNotZero: 1
87
+ };
88
+ const soloWeight = 1;
89
+ const countWeights = {
90
+ absolute: 2,
91
+ largest: 1,
92
+ smaller: 0.4
93
+ };
94
+ const preventionWeights = {
95
+ testaro: 50,
96
+ other: 100
97
+ };
98
+ const packageDetails = {};
99
+ const groupDetails = {
100
+ groups: {},
101
+ solos: {}
102
+ };
103
+ const summary = {
104
+ total: 0,
105
+ log: 0,
106
+ preventions: 0,
107
+ solos: 0,
108
+ groups: {}
109
+ };
110
+ const otherPackages = ['aatt', 'alfa', 'axe', 'ibm'];
111
+ const preventionScores = {};
112
+
113
+ // FUNCTIONS
114
+
115
+ // Adds to the count of issues of a kind discovered by a test package.
116
+ const addDetail = (actWhich, testID, addition = 1) => {
117
+ if (! packageDetails[actWhich]) {
118
+ packageDetails[actWhich] = {};
119
+ }
120
+ if (! packageDetails[actWhich][testID]) {
121
+ packageDetails[actWhich][testID] = 0;
122
+ }
123
+ packageDetails[actWhich][testID] += addition;
124
+ };
125
+ // Adds scores to a report.
126
+ exports.scorer = async report => {
127
+ // If there are any acts:
128
+ const {acts} = report;
129
+ if (Array.isArray(acts)) {
130
+ // If any of them are test acts:
131
+ const testActs = acts.filter(act => act.type === 'test');
132
+ if (testActs.length) {
133
+ // For each test act:
134
+ testActs.forEach(test => {
135
+ const {which} = test;
136
+ // Get the issue tally.
137
+ if (which === 'aatt') {
138
+ const issues = test.result;
139
+ if (issues && Array.isArray(issues)) {
140
+ issues.forEach(issue => {
141
+ const {type, id} = issue;
142
+ if (type && id) {
143
+ const typedID = `${type[0]}:${id}`;
144
+ addDetail(which, typedID);
145
+ }
146
+ });
147
+ }
148
+ }
149
+ else if (which === 'alfa') {
150
+ const issues = test.result;
151
+ if (issues && Array.isArray(issues)) {
152
+ issues.forEach(issue => {
153
+ const {rule} = issue;
154
+ if (rule) {
155
+ const {ruleID} = rule;
156
+ if (ruleID) {
157
+ addDetail(which, ruleID);
158
+ }
159
+ }
160
+ });
161
+ }
162
+ }
163
+ else if (which === 'axe') {
164
+ const tests = test.result && test.result.items;
165
+ if (tests && Array.isArray(tests)) {
166
+ tests.forEach(test => {
167
+ const {rule, elements} = test;
168
+ if (rule && Array.isArray(elements) && elements.length) {
169
+ addDetail(which, rule, elements.length);
170
+ }
171
+ });
172
+ }
173
+ }
174
+ else if (which === 'ibm') {
175
+ const result = test.result && test.result.content;
176
+ const {items} = result;
177
+ if (items && Array.isArray(items) && items.length) {
178
+ items.forEach(issue => {
179
+ const {ruleID} = issue;
180
+ if (ruleID) {
181
+ addDetail(which, ruleID);
182
+ }
183
+ });
184
+ }
185
+ }
186
+ else if (which === 'bulk') {
187
+ const count = test.result && test.result.visibleElements;
188
+ if (typeof count === 'number') {
189
+ const faultCount = Math.floor(count / 300);
190
+ addDetail('testaro', which, faultCount);
191
+ }
192
+ }
193
+ else if (which === 'embAc') {
194
+ const issueCounts = test.result && test.result.totals;
195
+ if (issueCounts) {
196
+ const counts = Object.values(issueCounts);
197
+ const total = counts.reduce((sum, current) => sum + current);
198
+ addDetail('testaro', which, total);
199
+ }
200
+ }
201
+ else if (which === 'focAll') {
202
+ const discrepancy = test.result && test.result.discrepancy;
203
+ if (discrepancy) {
204
+ addDetail('testaro', which, Math.abs(discrepancy));
205
+ }
206
+ }
207
+ else if (which === 'focInd') {
208
+ const issueTypes = test.result && test.result.totals && test.result.totals.types;
209
+ if (issueTypes) {
210
+ const missingCount = issueTypes.indicatorMissing && issueTypes.indicatorMissing.total;
211
+ const badCount = issueTypes.nonOutlinePresent && issueTypes.nonOutlinePresent.total;
212
+ const faultCount = Math.round(missingCount + badCount / 2);
213
+ if (faultCount) {
214
+ addDetail('testaro', which, faultCount);
215
+ }
216
+ }
217
+ }
218
+ else if (which === 'focOp') {
219
+ const issueTypes = test.result && test.result.totals && test.result.totals.types;
220
+ if (issueTypes) {
221
+ const noOpCount = issueTypes.onlyFocusable && issueTypes.onlyFocusable.total;
222
+ const noFocCount = issueTypes.onlyOperable && issueTypes.onlyOperable.total;
223
+ const faultCount = Math.round(noFocCount + noOpCount / 2);
224
+ if (faultCount) {
225
+ addDetail('testaro', which, faultCount);
226
+ }
227
+ }
228
+ }
229
+ else if (which === 'hover') {
230
+ const issues = test.result && test.result.totals;
231
+ if (issues) {
232
+ const {triggers, madeVisible, opacityChanged, opacityAffected, unhoverables} = issues;
233
+ const faultCount = Math.round(
234
+ 1 * triggers
235
+ + 0.5 * madeVisible
236
+ + 0.2 * opacityChanged
237
+ + 0.2 * opacityAffected
238
+ + 1 * unhoverables
239
+ );
240
+ if (faultCount) {
241
+ addDetail('testaro', which, faultCount);
242
+ }
243
+ }
244
+ }
245
+ else if (which === 'labClash') {
246
+ const mislabeledCount = test.result
247
+ && test.result.totals
248
+ && test.result.totals.mislabeled;
249
+ if (mislabeledCount) {
250
+ addDetail('testaro', which, mislabeledCount);
251
+ }
252
+ }
253
+ else if (which === 'linkUl') {
254
+ const issues = test.result && test.result.items && test.result.items.notUnderlined;
255
+ if (issues && issues.length) {
256
+ addDetail('testaro', which, issues.length);
257
+ }
258
+ }
259
+ else if (which === 'menuNav') {
260
+ const issueCount = test.result
261
+ && test.result.totals
262
+ && test.result.totals.navigations
263
+ && test.result.totals.navigations.all
264
+ && test.result.totals.navigations.all.incorrect;
265
+ if (issueCount && typeof issueCount === 'number') {
266
+ addDetail('testaro', which, issueCount);
267
+ }
268
+ }
269
+ else if (which === 'motion') {
270
+ const data = test.result;
271
+ if (data && data.bytes) {
272
+ const faultCount = Math.floor(
273
+ 5 * (data.meanLocalRatio - 1)
274
+ + 2 * (data.maxLocalRatio - 1)
275
+ + data.globalRatio - 1
276
+ + data.meanPixelChange / 10000
277
+ + data.maxPixelChange / 25000
278
+ + 30 * data.changeFrequency
279
+ );
280
+ addDetail('testaro', which, faultCount);
281
+ }
282
+ }
283
+ else if (which === 'radioSet') {
284
+ const counts = test.result && test.result.totals;
285
+ const {total, inSet} = counts;
286
+ if (total && typeof inSet === 'number' && total >= inSet) {
287
+ addDetail('testaro', which, total - inSet);
288
+ }
289
+ }
290
+ else if (which === 'role') {
291
+ const issueCount = test.result && test.result.badRoleElements;
292
+ if (issueCount && typeof issueCount === 'number') {
293
+ addDetail('testaro', which, issueCount);
294
+ }
295
+ }
296
+ else if (which === 'styleDiff') {
297
+ const counts = test.result && test.result.totals;
298
+ if (counts) {
299
+ // Identify objects having the tag-name totals and style distributions as properties.
300
+ const tagNameCounts = Object.values(counts);
301
+ // Identify an array of pairs of counts of excess styles and of nonplurality elements.
302
+ const faults = tagNameCounts.map(
303
+ item => {
304
+ const subtotals = item.subtotals ? item.subtotals : [item.total];
305
+ return [subtotals.length - 1, item.total - subtotals[0]];
306
+ }
307
+ );
308
+ // Fault count: 2 per excess style + 0.2 per nonplurality element.
309
+ const faultCount = Math.floor(faults.reduce(
310
+ (total, currentPair) => total + 2 * currentPair[0] + 0.2 * currentPair[1], 0
311
+ ));
312
+ addDetail('testaro', which, faultCount);
313
+ }
314
+ }
315
+ else if (which === 'tabNav') {
316
+ const issueCount = test.result
317
+ && test.result.totals
318
+ && test.result.totals.navigations
319
+ && test.result.totals.navigations.all
320
+ && test.result.totals.navigations.all.incorrect;
321
+ if (issueCount && typeof issueCount === 'number') {
322
+ addDetail('testaro', which, issueCount);
323
+ }
324
+ }
325
+ else if (which === 'zIndex') {
326
+ const issueCount = test.result && test.result.totals;
327
+ if (issueCount && typeof issueCount === 'number') {
328
+ addDetail('testaro', which, issueCount);
329
+ }
330
+ }
331
+ });
332
+ // Get the prevention scores and add them to the summary.
333
+ const actsPrevented = testActs.filter(test => test.result.prevented);
334
+ actsPrevented.forEach(act => {
335
+ if (otherPackages.includes(act.which)) {
336
+ preventionScores[act.which] = preventionWeights.other;
337
+ }
338
+ else {
339
+ preventionScores[`testaro-${act.which}`] = preventionWeights.testaro;
340
+ }
341
+ });
342
+ const preventionScore = Object
343
+ .values(preventionScores)
344
+ .reduce((sum, current) => sum + current, 0);
345
+ summary.preventions = preventionScore;
346
+ summary.total += preventionScore;
347
+ // Get data on test groups.
348
+ const testGroupsJSON = await fs.readFile('scoring/data/testGroups.json', 'utf8');
349
+ const testGroups = JSON.parse(testGroupsJSON);
350
+ // Use the data to populate groupDetails.groups.
351
+ const {tests} = testGroups;
352
+ const groupPackageIDs = Object.keys(tests);
353
+ groupPackageIDs.forEach(packageID => {
354
+ const packageTestIDs = Object.keys(tests[packageID]);
355
+ packageTestIDs.forEach(testID => {
356
+ const testData = tests[packageID][testID];
357
+ const {groupID, what} = testData;
358
+ if (! groupDetails.groups[groupID]) {
359
+ groupDetails.groups[groupID] = {};
360
+ }
361
+ if (! groupDetails.groups[groupID][packageID]) {
362
+ groupDetails.groups[groupID][packageID] = {};
363
+ }
364
+ groupDetails.groups[testData.groupID][packageID][testID] = {
365
+ what,
366
+ issueCount: 0
367
+ };
368
+ });
369
+ })
370
+ // Get the IDs of the packages whose tests report any issues.
371
+ const issuePackageIDs = Object.keys(packageDetails);
372
+ // For each such package:
373
+ issuePackageIDs.forEach(packageID => {
374
+ // Get the IDs of the tests in the package that report issues.
375
+ const issueTestIDs = Object.keys(packageDetails[packageID]);
376
+ // For each such test:
377
+ issueTestIDs.forEach(testID => {
378
+ // Get its group data, if any.
379
+ const testGroupData = tests[packageID][testID];
380
+ const issueCount = packageDetails[packageID][testID];
381
+ // If it is in a group:
382
+ if (testGroupData) {
383
+ // Add the issue count to the group details.
384
+ const {groupID} = testGroupData;
385
+ groupDetails.groups[groupID][packageID][testID].issueCount = issueCount;
386
+ }
387
+ // Otherwise, i.e. if the test is solo:
388
+ else {
389
+ // Add the issue count to the solo details.
390
+ if (! groupDetails.solos[packageID]) {
391
+ groupDetails.solos[packageID] = {};
392
+ }
393
+ groupDetails.solos[packageID][testID] = issueCount;
394
+ }
395
+ });
396
+ });
397
+ // Delete from the group details groups without any issues.
398
+ const groupIDs = Object.keys(groupDetails.groups);
399
+ groupIDs.forEach(groupID => {
400
+ const groupPackageData = Object.values(groupDetails.groups[groupID]);
401
+ if (
402
+ groupPackageData.every(datum => Object.values(datum).every(test => test.issueCount === 0))
403
+ ) {
404
+ delete groupDetails.groups[groupID];
405
+ }
406
+ });
407
+ // Get the group scores and add them to the summary.
408
+ const issueGroupIDs = Object.keys(groupDetails.groups);
409
+ const {absolute, largest, smaller} = countWeights;
410
+ issueGroupIDs.forEach(groupID => {
411
+ const issueCounts = [];
412
+ const groupPackageData = Object.values(groupDetails.groups[groupID]);
413
+ groupPackageData.forEach(packageDatum => {
414
+ const issueCountSum = Object
415
+ .values(packageDatum)
416
+ .reduce((sum, current) => sum + current.issueCount, 0);
417
+ issueCounts.push(issueCountSum);
418
+ });
419
+ issueCounts.sort((a, b) => b - a);
420
+ const groupScore = groupWeights[groupID] * (
421
+ absolute + largest * issueCounts[0] + smaller * issueCounts.slice(1).reduce(
422
+ (sum, current) => sum + current, 0
423
+ )
424
+ );
425
+ const roundedScore = Math.round(groupScore);
426
+ summary.groups[groupID] = roundedScore;
427
+ summary.total += roundedScore;
428
+ });
429
+ // Get the solo scores and add them to the summary.
430
+ const issueSoloPackageIDs = Object.keys(groupDetails.solos);
431
+ issueSoloPackageIDs.forEach(packageID => {
432
+ const testIDs = Object.keys(groupDetails.solos[packageID]);
433
+ testIDs.forEach(testID => {
434
+ const issueCount = groupDetails.solos[packageID][testID];
435
+ const issueScore = Math.round(soloWeight * issueCount);
436
+ summary.solos += issueScore;
437
+ summary.total += issueScore;
438
+ });
439
+ });
440
+ }
441
+ }
442
+ // Get the log score.
443
+ logScore = Math.floor(
444
+ logWeights.count * report.logCount
445
+ + logWeights.size * report.logSize
446
+ + logWeights.prohibited * report.prohibitedCount
447
+ + logWeights.visitTimeout * report.visitTimeoutCount
448
+ + logWeights.visitRejection * report.visitRejectionCount
449
+ );
450
+ summary.log = logScore;
451
+ summary.total += logScore;
452
+ // Add the score facts to the report.
453
+ report.score = {
454
+ scoreProcID,
455
+ logWeights,
456
+ groupWeights,
457
+ soloWeight,
458
+ countWeights,
459
+ preventionWeights,
460
+ packageDetails,
461
+ groupDetails,
462
+ preventionScores,
463
+ summary
464
+ };
465
+ };
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="author" content="Testilo">
7
+ <meta name="creator" content="Testilo">
8
+ <meta name="publisher" name="Testilo">
9
+ <meta name="description" content="comparison of accessibility scores from tsp10 procedure">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility score comparison</title>
12
+ <link rel="icon" href="favicon.png">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility score comparison</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>The table below compares 2 web pages on <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a>. The page names link to the pages on the web. The scores link to digests that explain in detail how the scores were computed.</p>
22
+ <p>The pages were:</p>
23
+ <div id="summary">
24
+ <p>Tested by <a href="https://www.npmjs.com/package/testaro">Testaro</a> with procedure <code>tp10</code></p>
25
+ <p>Scored by <a href="https://www.npmjs.com/package/testilo">Testilo</a> with procedure <code>sp10a</code></p>
26
+ <p>Digested by Testilo with procedure <code>dp10a</code></p>
27
+ <p>Compared by Testilo with procedure <code>cp0</code></p>
28
+ </div>
29
+ <p>The <code>tp10</code> procedure performs 808 tests on each page. Of these, 16 tests are custom tests defined by Testaro, and the others belong to packages of tests created by others.</p>
30
+ <p>Tests and scoring formulae are fallible and subjective. The reported faults merit investigation as potential opportunities for improved accessibility. But some may not actually harm accessibility, and some other accessibility faults may have escaped detection. Different reasonable procedures could yield different test results and different scores. Testaro and Testilo can be customized to fit different definitions and weightings of types of accessibility.</p>
31
+ <h2>Comparison</h2>
32
+ <table class="allBorder">
33
+ <caption>Accessibility scores of web pages</caption>
34
+ <thead>
35
+ <tr><th scope="col">Page</th><th scope="col" colspan="2">Score (lower is better)</tr>
36
+ </thead>
37
+ <tbody class="linkSmaller secondCellRight">
38
+ <tr><th scope="row"><a href="https://www.railpass.com/">Railpass</a></th><td><a href="digests/35k1r-railpass.html">1589</a></td><td aria-hidden="true"><svg width="100%" height="0.7em"><rect height="100%" width="23.159889228975366%" fill="red"></rect></svg></td></tr>
39
+ <tr><th scope="row"><a href="https://www.eurail.com/en">Eurail</a></th><td><a href="digests/3aee6-eurail.html">6861</a></td><td aria-hidden="true"><svg width="100%" height="0.7em"><rect height="100%" width="100%" fill="red"></rect></svg></td></tr>
40
+ </tbody>
41
+ </table>
42
+ <footer>
43
+ <p class="date">Produced <time itemprop="datePublished" datetime="2022-06-08">2022/06/08</time></p>
44
+ </footer>
45
+ </main>
46
+ </body>
47
+ </html>
@@ -64,6 +64,10 @@ legend {
64
64
  section:not(.wide) {
65
65
  max-width: 36rem;
66
66
  }
67
+ #summary > p {
68
+ margin: 0;
69
+ padding: 0;
70
+ }
67
71
  table {
68
72
  border-collapse: collapse;
69
73
  }