testilo 3.3.1 → 3.5.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 (39) hide show
  1. package/README.md +40 -0
  2. package/package.json +1 -1
  3. package/procs/compare/cp1/index.html +46 -0
  4. package/procs/compare/cp1/index.js +71 -0
  5. package/procs/digest/dp10a/index.html +2 -2
  6. package/procs/digest/dp10b/index.html +74 -0
  7. package/procs/digest/dp10b/index.js +130 -0
  8. package/procs/digest/dp10c/index.html +55 -0
  9. package/procs/digest/dp10c/index.js +129 -0
  10. package/procs/digest/dp11a/index.html +76 -0
  11. package/procs/digest/dp11a/index.js +127 -0
  12. package/procs/score/sp10b.js +507 -0
  13. package/procs/score/sp10c.js +465 -0
  14. package/procs/score/sp11a.js +1968 -0
  15. package/reports/digested/35k1r-railpass.html +1129 -843
  16. package/reports/digested/3aee6-eurail.html +2823 -2372
  17. package/reports/digested/dp10a/35k1r-railpass.html +9302 -0
  18. package/reports/digested/dp10a/3aee6-eurail.html +35782 -0
  19. package/reports/digested/style.css +2 -2
  20. package/reports/raw/3aee6-eurail.json +4 -2
  21. package/reports/scored/35k1r-railpass.json +559 -175
  22. package/reports/scored/3aee6-eurail.json +692 -173
  23. package/score.js +1 -1
  24. package/scoring/data/duplications.json +22 -22
  25. package/scoring/data/groups.json +335 -0
  26. package/scoring/data/packageRules/alfa.tsv +82 -0
  27. package/scoring/data/packageRules/axe.js +390 -0
  28. package/scoring/data/packageRules/ibm.tsv +159 -0
  29. package/scoring/data/packageRules/tenon.tsv +14 -0
  30. package/scoring/data/packageRules/wave.json +1617 -0
  31. package/scoring/data/packageRules/wave.tsv +55 -0
  32. package/scoring/data/testGroups.json +1067 -0
  33. package/scoring/{data → procs}/correlation.js +0 -0
  34. package/scoring/{data → procs}/dupCounts.js +0 -0
  35. package/scoring/procs/groups.js +61 -0
  36. package/scoring/{data → procs}/packageData.js +0 -0
  37. package/scoring/{data → procs}/packageIssues.js +0 -0
  38. package/scoring/procs/regroup.js +39 -0
  39. package/scoring/procs/tempgroup.js +96 -0
package/README.md CHANGED
@@ -23,6 +23,8 @@ Testilo includes some score procs, digest procs, and comparison procs. You can a
23
23
 
24
24
  ### Scoring
25
25
 
26
+ #### Process
27
+
26
28
  To score Testaro reports, execute the statement `node score abc xyz`. Replace (here and below) `abc` with the base of the name of the file containing the report, or the prefix of the file name. Replace `xyz` with the base of the name of the score proc.
27
29
 
28
30
  If you replace `abc` with the entire name base, Testilo will score one report. If you replace `abc` with a prefix (such as `35k1r-`), Testilo will score all the reports whose names begin with that prefix.
@@ -37,6 +39,44 @@ This procedure has some preconditions:
37
39
 
38
40
  When Testilo scores a report, Testilo saves the scored report in the directory whose relative path is the value of the `REPORTDIR_SCORED`. The scored report file has the same name as the original. The scored report has the same content as the original, plus a new property named `score`.
39
41
 
42
+ #### Procedures
43
+
44
+ Score proc `tsp09a` implements one possible algorithm for scoring results from Testaro sample script `tsp09`.
45
+
46
+ Score proc `sp10a` implements an algorithm similar to `tsp09a`, except for Testaro sample script `tp10`, which, unlike `tsp09`, includes the Tenon package.
47
+
48
+ Score proc `sp10b` likewise scores results from Testaro script `tp10`, but, unlike score proc `sp10a`, bases scores on _issues_ rather than tests. Here, an issue is a case of a fault or a suspected fault of a particular kind. For example, if there are 7 informative images without text alternatives, the count of issues of that kind is 7.
49
+
50
+ Because packages differ in how they identify the locations of issues, score proc `sp10b` does not try to establish whether an issue discovered by one test is the same as an issue discovered by another test. Instead, its algorithm is based on the presumption that the issues of a kind discovered by different tests are typically, but not always, subsets or supersets of one another. For example, if test A discovers 4 issues of kind X and test B discovers 6 issues of kind X, it is likely that the 4 discovered by A are also among the 6 discovered by B.
51
+
52
+ Reflecting this presumption, the contribution of any issue kind to a total score is based substantially on the largest count of issues of that kind discovered by any test. The first issue of a kind is weighted more heavily than each additional issue, because the existence of any issues of a particular kind, regardless of how many, tends to create usability barriers, remediation costs, and liability risks.
53
+
54
+ To a smaller extent, the total score is also affected by the counts of issues of the same kind discovered by other tests. This is because, when multiple tests discover a kind of issue:
55
+ - we can be more confident that there really are issues of that kind.
56
+ - the issues discovered by different tests might not fully overlap.
57
+ - there is less excuse for allowing issues of that kind to appear.
58
+ - the website owner is more likely to receive complaints or claims about issues of that kind.
59
+
60
+ Score proc `sp10b` uses the data in `scoring/data/testGroups.json` to identify _groups_ of tests, where the tests in any group are deemed to test for the same kind of issue.
61
+
62
+ Some tests have not been grouped. The proper treatment of those tests may be:
63
+ - to treat them as a group of 1, because they alone discover issues of some kind.
64
+ - to insert them into an existing group.
65
+
66
+ As long as they are not grouped, each issue discovered by one of those tests contributes a small amount to the total score.
67
+
68
+ The contribution also depends on how serious each kind of issue is deemed to be. Score proc `sp10b` assigns a weight to each test group. Weighting could be even more granular: The Axe-core package attributes a seriousness to each issue, so that two issues of the same kind can differ in seriousness. But score proc `sp10b` ignores that Axe-core rating.
69
+
70
+ #### Prevention
71
+
72
+ Testaro reports an error when it is unable to perform a test on a host. A score proc can take such errors into account. Score procs `sp10a`, `sp10b`, and `sp10c` presume that a failure of Testaro to perform a test is caused by the host. Thus, it treats such a failure as a case of the host preventing Testaro from performing a test. These score procs add amounts for such presumed preventions to total scores, guessing the score contributions of the tests if they had not been prevented plus an amount representing the estimated impact on accessibility of the prevention _per se_.
73
+
74
+ One motivation for penalizing prevention is the assumption that measurement of success is an essential contributor to success, so preventing measurement of accessibility interferes with the achievement of accessibility.
75
+
76
+ A second motivation is the presumtion that assistive technologies are unpredictable, changing, and not fundamentally different from testing technologies. Therefore, a host that prevents a testing technology is at risk for preventing an assistive technology, too, and thereby interfering with accessibility.
77
+
78
+ Users who prefer to disregard prevention in scoring can create score procs that do that. It is not obvious what it means to disregard a test that could not be performed. Treating it as contributing 0 to a score arguably is not neutral, but rather rewards prevention.
79
+
40
80
  ### Digesting
41
81
 
42
82
  To make scored Testaro reports more useful for humans, Testilo can create digests of scored reports. A digest is an HTML document (a web page) summarizing and explaining the findings, with the scored report appended to it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "3.3.1",
3
+ "version": "3.5.0",
4
4
  "description": "Client that scores and digests Testaro reports",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,46 @@
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">
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 __pageCount__ 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></p>
25
+ <p>Scored by <a href="https://www.npmjs.com/package/testilo">Testilo</a> with procedure <code>sp10c</code></p>
26
+ <p>Digested by Testilo with procedure <code>dp10c</code></p>
27
+ <p>Compared by Testilo with procedure <code>cp1</code></p>
28
+ </div>
29
+ <p>The Testaro procedure performs about 600 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
+ __tableBody__
39
+ </tbody>
40
+ </table>
41
+ <footer>
42
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
43
+ </footer>
44
+ </main>
45
+ </body>
46
+ </html>
@@ -0,0 +1,71 @@
1
+ /*
2
+ cp1.js
3
+ Returns a query for an HTML page including a bar-graph table.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ // Module to keep secrets local.
9
+ require('dotenv').config();
10
+ // Module to access files.
11
+ const fs = require('fs/promises');
12
+
13
+ // ########## CONSTANTS
14
+
15
+ const reportDirScored = process.env.REPORTDIR_SCORED || 'reports/scored';
16
+ const query = {};
17
+
18
+ // ########## FUNCTIONS
19
+
20
+ // Gets data on the hosts and their scores and adds query parameter.
21
+ const getData = async () => {
22
+ const reportDirAbs = `${__dirname}/../../../${reportDirScored}`;
23
+ const reportFileNamesAll = await fs.readdir(reportDirAbs);
24
+ const reportFileNamesSource = reportFileNamesAll.filter(fileName => fileName.endsWith('.json'));
25
+ const pageCount = reportFileNamesSource.length;
26
+ const bodyData = [];
27
+ for (const fileName of reportFileNamesSource) {
28
+ const fileJSON = await fs.readFile(`${reportDirAbs}/${fileName}`, 'utf8');
29
+ const file = JSON.parse(fileJSON);
30
+ const {id, script, score} = file;
31
+ bodyData.push({
32
+ id,
33
+ org: script.what,
34
+ url: script.commands.find(command => command.type === 'url').which,
35
+ score: score.summary.total
36
+ });
37
+ };
38
+ return {
39
+ pageCount,
40
+ bodyData
41
+ }
42
+ };
43
+ // Gets the maximum score.
44
+ const getMaxScore = tableData => tableData.reduce((max, item) => Math.max(max, item.score), 0);
45
+ // Converts report data to a table body.
46
+ const getTableBody = async bodyData => {
47
+ const maxScore = getMaxScore(bodyData);
48
+ const rows = bodyData
49
+ .sort((a, b) => a.score - b.score)
50
+ .map(item => {
51
+ const {id, org, url, score} = item;
52
+ const pageCell = `<th scope="row"><a href="${url}">${org}</a></th>`;
53
+ const numCell = `<td><a href="digests/${id}.html">${score}</a></td>`;
54
+ const barWidth = 100 * score / maxScore;
55
+ const bar = `<rect height="100%" width="${barWidth}%" fill="red"></rect>`;
56
+ const barCell = `<td aria-hidden="true"><svg width="100%" height="0.7em">${bar}</svg></td>`;
57
+ const row = `<tr>${pageCell}${numCell}${barCell}</tr>`;
58
+ return row;
59
+ });
60
+ return rows.join('\n ');
61
+ };
62
+ // Returns a query for a comparative table.
63
+ exports.getQuery = async () => {
64
+ const data = await getData();
65
+ query.pageCount = data.pageCount;
66
+ query.tableBody = await getTableBody(data.bodyData);
67
+ const date = new Date();
68
+ query.dateISO = date.toISOString().slice(0, 10);
69
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
70
+ return query;
71
+ };
@@ -16,8 +16,8 @@
16
16
  <main>
17
17
  <header>
18
18
  <h1>Accessibility test digest</h1>
19
- <h2>Summary</h2>
20
- <div id="summary">
19
+ <h2>Synopsis</h2>
20
+ <div id="synopsis">
21
21
  <p>Page: __org__</p>
22
22
  <p>URL: __url__</p>
23
23
  <p>Score: __totalScore__</p>
@@ -0,0 +1,74 @@
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="report of accessibility testing of a web page">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility test digest</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 test digest</h1>
19
+ <h2>Synopsis</h2>
20
+ <div id="synopsis">
21
+ <p>Page: __org__</p>
22
+ <p>URL: __url__</p>
23
+ <p>Score: __totalScore__</p>
24
+ <p>Tested by: Testaro, procedure <code>tp10</code></p>
25
+ <p>Scored by: Testilo, procedure <code>sp10b</code></p>
26
+ <p>Digested by: Testilo, procedure <code>dp10b</code></p>
27
+ </div>
28
+ </header>
29
+ <h2>Introduction</h2>
30
+ <p>The <a href="https://www.npmjs.com/package/testaro">Testaro</a> application used its <code>tp10</code> testing procedure to test the <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/"><dfn>accessibility</dfn></a> (barrier-free design and coding) of the __org__ web page at <a href="__url__">__url__</a> on __dateSlash__. The procedure performed 808 tests. Of these, 16 are custom tests defined by Testaro, and the others belong to six other packages of tests:</p>
31
+ <ul>
32
+ <li><a href="https://www.npmjs.com/package/aatt">AATT</a> by PayPal running <a href="https://marketplace.squiz.net/extensions/html-codesniffer">HTMLCodeSniffer</a> by Squiz</li>
33
+ <li><a href="https://github.com/Siteimprove/alfa">Alfa</a> by Siteimprove</li>
34
+ <li><a href="https://www.npmjs.com/package/axe-core">Axe-core</a> by Deque</li>
35
+ <li><a href="https://github.com/IBMa/equal-access">Equal Access</a> by IBM</li>
36
+ <li><a href="https://tenon.io/documentation/apiv2.php">Tenon</a> by Level Access</li>
37
+ <li><a href="https://wave.webaim.org/api/">WAVE</a> by WebAIM</li>
38
+ </ul>
39
+ <p>Testaro produced a report enumerating the test results.</p>
40
+ <p><a href="https://www.npmjs.com/package/testilo">Testilo</a> used its <code>sp10b</code> scoring procedure to compute partial and total scores for the page. The total score is __totalScore__ (where 0 is the best possible score). The scored report is appended below.</p>
41
+ <p>Finally, Testilo used procedure <code>dp10b</code> to produce this digest, explaining how <code>sp10b</code> computed the scores.</p>
42
+ <h2>Score summary</h2>
43
+ <table class="allBorder secondCellRight">
44
+ <caption>Score components</caption>
45
+ <tbody class="headersLeft">
46
+ __scoreRows__
47
+ </tbody>
48
+ </table>
49
+ <h2>Issue summary</h2>
50
+ <h3>Special issues</h3>
51
+ __specialSummary__
52
+ <h3>Classified issues</h3>
53
+ <p>The classified issues reported by tests are summarized below.</p>
54
+ __groupSummary__
55
+ <h2>Discussion</h2>
56
+ <p>Although there are widely accepted <a href="https://www.w3.org/WAI/standards-guidelines/">accessibility standards</a>, there is still no unanimity about how to define, test, and quantify accessibility. The failures reported in this digest merit investigation as <strong>potential</strong> opportunities for improved accessibility. But you may conclude that some of the failures do not actually harm accessibility, and some substantial accessibility faults can escape detection by any of these tests. Likewise, you may disagree with weightings and formulas of scoring procedure <code>sp10b</code>, or question whether it is appropriate even to assign accessibility scores to a web page. This digest describes only one interpretation of the page&rsquo;s accessibility. You can modify and extend Testaro and Testilo to fit other understandings.</p>
57
+ <p>Scoring procedure <code>sp10b</code> bases scores on:</p>
58
+ <ul>
59
+ <li>how many categories of issues were reported</li>
60
+ <li>how many issues were reported per category</li>
61
+ <li>the largest count of issues of a category reported by a single test</li>
62
+ <li>how many tests reported the issues</li>
63
+ <li>how important each issue is deemed to be</li>
64
+ </ul>
65
+ <p>Most issues are simply counted, but some are complex and get a <q>count</q> that is really the result of a formula. The exact rules for scoring are in the <a href="https://github.com/jrpool/testilo/blob/main/procs/score/sp10b.js"><code>sp10b.js</code> file</a> that performs the scoring.</p>
66
+ <p>Weights used in the calculations are contained in the appended report in the <code>score</code> block. The <code>groupWeights</code> block within it contains the weights applied to the classified issues, and the <code>countWeights</code> block within it contains the amounts those weights are multiplied by or added to. For example, if <code>roleBad</code> issues are reported, the <code>roleBad</code> score is the <code>absolute</code> amount (2), plus the largest count of those issues reported by any test multiplied by the <code>largest</code> amount (1), plus the total count of those issues reported by all other tests multiplied by the <code>smaller</code> amount (0.4), all multiplied by the <code>roleBad</code> weight (3).</p>
67
+ <h2>Report</h2>
68
+ <pre>__report__</pre>
69
+ <footer>
70
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
71
+ </footer>
72
+ </main>
73
+ </body>
74
+ </html>
@@ -0,0 +1,130 @@
1
+ /*
2
+ index: digester for scoring procedure sp10b.
3
+ Creator of parameters for substitution into index.html.
4
+ Usage example: node digest 35k1r-railpass dp10b
5
+ Arguments:
6
+ 0. report. Scored report.
7
+ 1. query. Object to which this module will add properties.
8
+ */
9
+
10
+ // CONSTANTS
11
+
12
+ // Newlines with indentations.
13
+ const joiner = '\n ';
14
+ const innerJoiner = '\n ';
15
+ const specialMessages = {
16
+ log: 'This is based on the amount of browser logging during the tests. Browsers usually log messages only when pages contain erroneous code.',
17
+ preventions: 'This is based on tests that the page did not allow to be run. That impedes accessibility progress and risks interfering with tools that users with disabilities need.',
18
+ solos: 'This is based on issues reported by unclassified tests. Details are in the report.'
19
+ };
20
+
21
+ // FUNCTIONS
22
+
23
+ // Makes strings HTML-safe.
24
+ const htmlEscape = textOrNumber => textOrNumber
25
+ .toString()
26
+ .replace(/&/g, '&amp;')
27
+ .replace(/</g, '&lt;');
28
+ // Gets a row of the score-summary table.
29
+ const getScoreRow = (component, score) => `<tr><th>${component}</th><td>${score}</td></tr>`;
30
+ // Gets the start of a paragraph about a special score.
31
+ const getSpecialPStart = (summary, scoreID) =>
32
+ `<p><span class="componentID">${scoreID}</span>: Score ${summary[scoreID]}.`;
33
+ // Adds parameters to a query for a digest.
34
+ exports.makeQuery = (report, query) => {
35
+ // Add an HTML-safe copy of the host report to the query to be appended to the digest.
36
+ const {script, host, score} = report;
37
+ const reportJSON = JSON.stringify(report, null, 2);
38
+ const reportJSONSafe = htmlEscape(reportJSON);
39
+ query.report = reportJSONSafe;
40
+ // Add the job data to the query.
41
+ query.dateISO = report.endTime.slice(0, 10);
42
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
43
+ if (host && host.what && host.which) {
44
+ query.org = host.what;
45
+ query.url = host.which;
46
+ }
47
+ else {
48
+ const firstURLCommand = script.commands.find(command => command.type === 'url');
49
+ if (firstURLCommand && firstURLCommand.what && firstURLCommand.which) {
50
+ query.org = firstURLCommand.what;
51
+ query.url = firstURLCommand.which;
52
+ }
53
+ else {
54
+ console.log('ERROR: host missing or invalid');
55
+ return;
56
+ }
57
+ }
58
+ const {groupDetails, summary} = score;
59
+ if (typeof summary.total === 'number') {
60
+ query.totalScore = summary.total;
61
+ }
62
+ else {
63
+ console.log('ERROR: missing or invalid total score');
64
+ return;
65
+ }
66
+ // Add the total and any special rows of the score-summary table to the query.
67
+ const scoreRows = [];
68
+ const specialComponentIDs = ['log', 'preventions', 'solos'];
69
+ ['total'].concat(specialComponentIDs).forEach(item => {
70
+ if (summary[item]) {
71
+ scoreRows.push(getScoreRow(item, summary[item]));
72
+ }
73
+ });
74
+ // Add the group rows of the score-summary table to the query.
75
+ const {groups} = summary;
76
+ const groupIDs = Object.keys(groups);
77
+ groupIDs.sort((a, b) => groups[b] - groups[a]);
78
+ groupIDs.forEach(groupID => {
79
+ scoreRows.push(getScoreRow(`${groupID}`, groups[groupID]));
80
+ });
81
+ query.scoreRows = scoreRows.join(innerJoiner);
82
+ // If the score has any special components:
83
+ const scoredSpecialIDs = specialComponentIDs.filter(item => summary[item]);
84
+ if (scoredSpecialIDs.length) {
85
+ // Add paragraphs about them for the issue summary to the query.
86
+ const specialPs = [];
87
+ scoredSpecialIDs.forEach(id => {
88
+ specialPs.push(`${getSpecialPStart(summary, id)} ${specialMessages[id]}`);
89
+ });
90
+ query.specialSummary = specialPs.join(joiner);
91
+ }
92
+ // Otherwise, i.e. if the score has no special components:
93
+ else {
94
+ // Add a paragraph stating this for the issue summary to the query.
95
+ query.specialSummary = '<p>No special issues contributed to the score.</p>'
96
+ }
97
+ // If the score has any classified issues as components:
98
+ if (groupIDs.length) {
99
+ // Add paragraphs about them for the special summary to the query.
100
+ const groupSummaryItems = [];
101
+ groupIDs.forEach(id => {
102
+ const groupP = `<p><span class="componentID">${id}</span>: Score ${summary.groups[id]}. Issues reported by tests in this category:</p>`;
103
+ const groupListItems = [];
104
+ const groupData = groupDetails.groups[id];
105
+ const packageIDs = Object.keys(groupData);
106
+ packageIDs.forEach(packageID => {
107
+ const testIDs = Object.keys(groupData[packageID]);
108
+ testIDs.forEach(testID => {
109
+ const testData = groupData[packageID][testID];
110
+ const {issueCount} = testData;
111
+ const issueNoun = issueCount !== 1 ? 'issues' : 'issue';
112
+ const listItem = `<li>${issueCount} ${issueNoun} reported by package <code>${packageID}</code>, test <code>${testID}</code> (${testData.what})</li>`;
113
+ groupListItems.push(listItem);
114
+ });
115
+ });
116
+ const groupList = [
117
+ '<ul>',
118
+ groupListItems.join('\n '),
119
+ '</ul>'
120
+ ].join(joiner);
121
+ groupSummaryItems.push(groupP, groupList);
122
+ });
123
+ query.groupSummary = groupSummaryItems.join(joiner);
124
+ }
125
+ // Otherwise, i.e. if the score has no classified issues as components:
126
+ else {
127
+ // Add a paragraph stating this for the group summary to the query.
128
+ query.groupSummary = '<p>No classified issues contributed to the score.</p>'
129
+ }
130
+ };
@@ -0,0 +1,55 @@
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="report of accessibility testing of a web page">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility test digest</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 test digest</h1>
19
+ <h2>Synopsis</h2>
20
+ <div id="synopsis">
21
+ <p>Page: __org__</p>
22
+ <p>URL: __url__</p>
23
+ <p>Score: __totalScore__</p>
24
+ <p>Tested by: Testaro, script <code>__scriptID__</code></p>
25
+ <p>Scored by: Testilo, procedure <code>sp10c</code></p>
26
+ <p>Digested by: Testilo, procedure <code>dp10c</code></p>
27
+ </div>
28
+ </header>
29
+ <h2>Introduction</h2>
30
+ <p>The <code>__scriptID__</code> <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a> testing script was executed by <a href="https://www.npmjs.com/package/testaro">Testaro</a> on the __org__ web page arrived at in a transaction that began at <a href="__url__">__url__</a>, on __dateSlash__. The procedure performed custom tests defined by Testaro and tests belonging to packages created by others. Testaro produced a report enumerating the test results.</p>
31
+ <p><a href="https://www.npmjs.com/package/testilo">Testilo</a> used its <code>sp10c</code> scoring procedure to compute partial and total scores for the page. The total score is __totalScore__ (where 0 is the best possible score). The scored report is appended below.</p>
32
+ <p>Finally, Testilo used procedure <code>dp10c</code> to produce this digest, explaining how <code>sp10c</code> computed the scores.</p>
33
+ <p>Disclaimer: Even though there are widely accepted accessibility standards, there is still no unanimity about how to define, test for, and quantify accessibility. The failures reported below merit investigation as <strong>potential</strong> opportunities for improved accessibility. But you may conclude that some of the failures do not actually harm accessibility, and some substantial accessibility faults can escape detection by any of these tests. Likewise, you may not agree with weightings and formulas of scoring procedure <code>sp10b</code>, or even question whether it is appropriate to assign accessibility scores to a web page. This digest describes only one interpretation of the page&rsquo;s accessibility.</p>
34
+ <h2>Score summary</h2>
35
+ <p>The total score and its components were:</p>
36
+ <table class="allBorder secondCellRight">
37
+ <caption>Score components</caption>
38
+ <tbody class="headersLeft">
39
+ __scoreRows__
40
+ </tbody>
41
+ </table>
42
+ <h2>Issue summary</h2>
43
+ <h3>Special issues</h3>
44
+ __specialSummary__
45
+ <h3>Classified issues</h3>
46
+ <p>The classified issues reported by tests are summarized below.</p>
47
+ __groupSummary__
48
+ <h2>Report</h2>
49
+ <pre>__report__</pre>
50
+ <footer>
51
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
52
+ </footer>
53
+ </main>
54
+ </body>
55
+ </html>
@@ -0,0 +1,129 @@
1
+ /*
2
+ index: digester for scoring procedure sp10b.
3
+ Creator of parameters for substitution into index.html.
4
+ Usage example: node digest 35k1r-railpass dp10b
5
+ Arguments:
6
+ 0. report. Scored report.
7
+ 1. query. Object to which this module will add properties.
8
+ */
9
+
10
+ // CONSTANTS
11
+
12
+ // Newlines with indentations.
13
+ const joiner = '\n ';
14
+ const innerJoiner = '\n ';
15
+ const specialMessages = {
16
+ log: 'This is based on the amount of browser logging during the tests. Browsers usually log messages only when pages contain erroneous code.',
17
+ preventions: 'This is based on tests that the page did not allow to be run. That impedes accessibility progress and risks interfering with tools that users with disabilities need.',
18
+ solos: 'This is based on issues reported by unclassified tests. Details are in the report.'
19
+ };
20
+
21
+ // FUNCTIONS
22
+
23
+ // Makes strings HTML-safe.
24
+ const htmlEscape = textOrNumber => textOrNumber
25
+ .toString()
26
+ .replace(/&/g, '&amp;')
27
+ .replace(/</g, '&lt;');
28
+ // Gets a row of the score-summary table.
29
+ const getScoreRow = (component, score) => `<tr><th>${component}</th><td>${score}</td></tr>`;
30
+ // Gets the start of a paragraph about a special score.
31
+ const getSpecialPStart
32
+ = (summary, scoreID) => `<p><span class="componentID">log</span>: Score ${summary[scoreID]}.`;
33
+ // Adds parameters to a query for a digest.
34
+ exports.makeQuery = (report, query) => {
35
+ // Add an HTML-safe copy of the host report to the query to be appended to the digest.
36
+ const {script, host, score} = report;
37
+ const reportJSON = JSON.stringify(report, null, 2);
38
+ const reportJSONSafe = htmlEscape(reportJSON);
39
+ query.report = reportJSONSafe;
40
+ // Add the job data to the query.
41
+ query.dateISO = report.endTime.slice(0, 10);
42
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
43
+ query.scriptID = script.id;
44
+ if (host && host.what && host.which) {
45
+ query.org = host.what;
46
+ query.url = host.which;
47
+ }
48
+ else {
49
+ const firstURLCommand = script.commands.find(command => command.type === 'url');
50
+ if (firstURLCommand && firstURLCommand.what && firstURLCommand.which) {
51
+ query.org = script.what;
52
+ query.url = firstURLCommand.which;
53
+ }
54
+ else {
55
+ console.log('ERROR: host missing or invalid');
56
+ return;
57
+ }
58
+ }
59
+ const {groupDetails, summary} = score;
60
+ if (typeof summary.total === 'number') {
61
+ query.totalScore = summary.total;
62
+ }
63
+ else {
64
+ console.log('ERROR: missing or invalid total score');
65
+ return;
66
+ }
67
+ // Add the total and any special rows of the score-summary table to the query.
68
+ const scoreRows = [];
69
+ const specialComponentIDs = ['log', 'preventions', 'solos'];
70
+ ['total'].concat(specialComponentIDs).forEach(item => {
71
+ if (summary[item]) {
72
+ scoreRows.push(getScoreRow(item, summary[item]));
73
+ }
74
+ });
75
+ // Add the group rows of the score-summary table to the query.
76
+ const {groups} = summary;
77
+ const groupIDs = Object.keys(groups);
78
+ groupIDs.sort((a, b) => groups[b] - groups[a]);
79
+ groupIDs.forEach(groupID => {
80
+ scoreRows.push(getScoreRow(`group ${groupID}`, groups[groupID]));
81
+ });
82
+ query.scoreRows = scoreRows.join(innerJoiner);
83
+ // If the score has any special components:
84
+ const scoredSpecialIDs = specialComponentIDs.filter(item => summary[item]);
85
+ if (scoredSpecialIDs.length) {
86
+ // Add paragraphs about them for the issue summary to the query.
87
+ const specialPs = [];
88
+ scoredSpecialIDs.forEach(id => {
89
+ specialPs.push(`${getSpecialPStart(summary, id)} ${specialMessages[id]}`);
90
+ });
91
+ query.specialSummary = specialPs.join(joiner);
92
+ }
93
+ // Otherwise, i.e. if the score has no special components:
94
+ else {
95
+ // Add a paragraph stating this for the issue summary to the query.
96
+ query.specialSummary = '<p>No special issues contributed to the score.</p>'
97
+ }
98
+ // If the score has any classified issues as components:
99
+ if (groupIDs.length) {
100
+ // Add paragraphs about them for the special summary to the query.
101
+ const groupSummaryItems = [];
102
+ groupIDs.forEach(id => {
103
+ const groupP = `<p><span class="componentID">${id}</span>: Score ${summary.groups[id]}. Issues reported by tests in this category:</p>`;
104
+ const groupListItems = [];
105
+ const groupData = groupDetails.groups[id];
106
+ const packageIDs = Object.keys(groupData);
107
+ packageIDs.forEach(packageID => {
108
+ const testIDs = Object.keys(groupData[packageID]);
109
+ testIDs.forEach(testID => {
110
+ const testData = groupData[packageID][testID];
111
+ const listItem = `<li>${testData.issueCount} issues reported by package ${packageID}, test ${testID} (${testData.what})</li>`;
112
+ groupListItems.push(listItem);
113
+ });
114
+ });
115
+ const groupList = [
116
+ '<ul>',
117
+ groupListItems.join('\n '),
118
+ '</ul>'
119
+ ].join(joiner);
120
+ groupSummaryItems.push(groupP, groupList);
121
+ });
122
+ query.groupSummary = groupSummaryItems.join(joiner);
123
+ }
124
+ // Otherwise, i.e. if the score has no classified issues as components:
125
+ else {
126
+ // Add a paragraph stating this for the group summary to the query.
127
+ query.groupSummary = '<p>No classified issues contributed to the score.</p>'
128
+ }
129
+ };