testilo 3.9.6-git → 3.9.9
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
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
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><strong>Page</strong>: __org__</p>
|
|
22
|
+
<p><strong>URL</strong>: __url__</p>
|
|
23
|
+
<p><strong>Score</strong>: __totalScore__</p>
|
|
24
|
+
<p><strong>Tested by</strong>: Testaro, procedure <code>tpA11yMessage</code></p>
|
|
25
|
+
<p><strong>Scored by</strong>: Testilo, procedure <code>spA11yMessage</code></p>
|
|
26
|
+
<p><strong>Digested by</strong>: Testilo, procedure <code>dpA11yMessage</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>tpA11yMessage</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 estimated how easily a user who finds an accessibility issue on the page can find a method for reporting that issue to the owner of the page. Testaro produced a report.</p>
|
|
31
|
+
<p><a href="https://www.npmjs.com/package/testilo">Testilo</a> processed the report and used the <code>spA11yMessage</code> scoring procedure to compute a score for the page. The total score is __totalScore__ (where 0 is the worst and 12 is the best possible score). The scored report is appended below.</p>
|
|
32
|
+
<p>Finally, Testilo used procedure <code>dpA11yMessage</code> to produce this digest, briefly explaining how <code>spA11yMessage</code> computed the scores.</p>
|
|
33
|
+
<h2>Score summary</h2>
|
|
34
|
+
<table class="allBorder secondCellRight">
|
|
35
|
+
<caption>Score components</caption>
|
|
36
|
+
<tbody class="headersLeft">
|
|
37
|
+
__scoreRows__
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
<h2>Score components</h2>
|
|
41
|
+
__scoreComponents__
|
|
42
|
+
<h2>Discussion</h2>
|
|
43
|
+
<p>The test assumes that the user, noticing an accessibility issue on the page, will look for a link named <q>Accessibility</q>, <q>Accessibility Statement</q>, or the like. The user will click that link, which will open an accessibility page, whose title and first heading will identify it as such. On that page, the user will expect links that let the user contact the site owner to report the issue. Ideally, there will be an email link and a telephone link, both identifiable as accessibility-related. The more completely all these expectations are fulfilled, the greater the awarded score.</p>
|
|
44
|
+
<p>Here, in brief, is how <code>spA11yMessage</code> computes a score for a page.</p>
|
|
45
|
+
<p>Each score component begins as 0.</p>
|
|
46
|
+
<ul>
|
|
47
|
+
<li>The <code>page</code> score becomes 2 if the page can be loaded. If it cannot, the test ends.</li>
|
|
48
|
+
<li>The <code>a11yLink</code> score becomes 2 if there is a clickable link on the page whose text content includes <q>accessibility</q> (case-insensitively). If there is no such link, the test ends.</li>
|
|
49
|
+
<li>The <code>title</code> score becomes 2 if the link opens a page whose title includes <q>accessibility</q> (case-insensitively).</li>
|
|
50
|
+
<li>The <code>heading</code> score becomes 1 if that page has exactly 1 first-level heading.</li>
|
|
51
|
+
<li>If the <code>heading</code> score is 1, it becomes 2 if that heading has text content that includes <q>accessibility</q> (case-insensitively).</li>
|
|
52
|
+
<li>The <code>mailLink</code> score increases by 1 if that page contains a link with an email destination (i.e. an <code>href</code> with a value starting with <code>mailto:</code>).</li>
|
|
53
|
+
<li>The <code>mailLink</code> score increases by 2 more if one of the email links has text content that includes <q>accessibility</q> (case-insensitively).</li>
|
|
54
|
+
<li>If none of the email links has text content including <q>accessibility</q> but <q>accessibility</q> appears in the text content of the parent of any of the email links, the <code>mailLink</code> score increases by 1 more.</li>
|
|
55
|
+
<li>The same computations as for the <code>mailLink</code> score are performed on links with telephone destinations (i.e. an <code>href</code> with a value starting with <code>tel:</code>), to determine the <code>telLink</code> score.</li>
|
|
56
|
+
<li>The total score is the sum of the above scores.</li>
|
|
57
|
+
</ul>
|
|
58
|
+
<p>The precise rules of <code>spA11yMessage</code> are found in the <a href="https://github.com/jrpool/testilo/blob/main/procs/score/spA11yMessage.js">code itself</a>.</p>
|
|
59
|
+
<h2>Report</h2>
|
|
60
|
+
<pre>__report__</pre>
|
|
61
|
+
<footer>
|
|
62
|
+
<p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
|
|
63
|
+
</footer>
|
|
64
|
+
</main>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index: digester for scoring procedure spA11yMessage.
|
|
3
|
+
Creator of parameters for substitution into index.html.
|
|
4
|
+
Usage example for selected files in REPORTDIR_SCORED: node digest dpA11yMessage 35k1r
|
|
5
|
+
Usage example for all files in REPORTDIR_SCORED: node digest dpA11yMessage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// CONSTANTS
|
|
9
|
+
|
|
10
|
+
// Newlines with indentations.
|
|
11
|
+
const joiner = '\n ';
|
|
12
|
+
const innerJoiner = '\n ';
|
|
13
|
+
|
|
14
|
+
// FUNCTIONS
|
|
15
|
+
|
|
16
|
+
// Makes strings HTML-safe.
|
|
17
|
+
const htmlEscape = textOrNumber => textOrNumber
|
|
18
|
+
.toString()
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<');
|
|
21
|
+
// Gets a row of the score-summary table.
|
|
22
|
+
const getScoreRow = (component, score) => `<tr><th>${component}</th><td>${score}</td></tr>`;
|
|
23
|
+
// Adds parameters to a query for a digest.
|
|
24
|
+
exports.makeQuery = (report, query) => {
|
|
25
|
+
// Add an HTML-safe copy of the host report to the query to be appended to the digest.
|
|
26
|
+
const {script, host, score} = report;
|
|
27
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
28
|
+
const reportJSONSafe = htmlEscape(reportJSON);
|
|
29
|
+
query.report = reportJSONSafe;
|
|
30
|
+
// Add the job data to the query.
|
|
31
|
+
query.dateISO = report.endTime.slice(0, 10);
|
|
32
|
+
query.dateSlash = query.dateISO.replace(/-/g, '/');
|
|
33
|
+
if (host && host.what && host.which) {
|
|
34
|
+
query.org = host.what;
|
|
35
|
+
query.url = host.which;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const firstURLCommand = script.commands.find(command => command.type === 'url');
|
|
39
|
+
if (firstURLCommand && firstURLCommand.what && firstURLCommand.which) {
|
|
40
|
+
query.org = firstURLCommand.what;
|
|
41
|
+
query.url = firstURLCommand.which;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log('ERROR: host missing or invalid');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const {groupDetails, summary} = score;
|
|
49
|
+
const {total, groups} = summary;
|
|
50
|
+
if (typeof total === 'number') {
|
|
51
|
+
query.totalScore = total;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('ERROR: missing or invalid total score');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Add the total to the query.
|
|
58
|
+
const scoreRows = [getScoreRow('total', summary(['total']))];
|
|
59
|
+
// Add the group rows of the score-summary table to the query.
|
|
60
|
+
groups.forEach(group => {
|
|
61
|
+
scoreRows.push(getScoreRow(`${group.groupName}`, group.score));
|
|
62
|
+
});
|
|
63
|
+
query.scoreRows = scoreRows.join(innerJoiner);
|
|
64
|
+
// If the score has any special components:
|
|
65
|
+
const scoredSpecialIDs = specialComponentIDs.filter(item => summary[item]);
|
|
66
|
+
if (scoredSpecialIDs.length) {
|
|
67
|
+
// Add paragraphs about them for the issue summary to the query.
|
|
68
|
+
const specialPs = [];
|
|
69
|
+
scoredSpecialIDs.forEach(id => {
|
|
70
|
+
specialPs.push(`${getSpecialPStart(summary, id)} ${specialMessages[id]}`);
|
|
71
|
+
});
|
|
72
|
+
query.specialSummary = specialPs.join(joiner);
|
|
73
|
+
}
|
|
74
|
+
// Otherwise, i.e. if the score has no special components:
|
|
75
|
+
else {
|
|
76
|
+
// Add a paragraph stating this for the issue summary to the query.
|
|
77
|
+
query.specialSummary = '<p>No special issues contributed to the score.</p>'
|
|
78
|
+
}
|
|
79
|
+
// If the score has any classified issues as components:
|
|
80
|
+
if (groups.length) {
|
|
81
|
+
// Add paragraphs about them for the special summary to the query.
|
|
82
|
+
const groupSummaryItems = [];
|
|
83
|
+
groups.forEach(group => {
|
|
84
|
+
const {groupName, score} = group;
|
|
85
|
+
const groupP = `<p><span class="componentID">${groupName}</span>: Score ${score}. Issues reported by tests in this category:</p>`;
|
|
86
|
+
const groupListItems = [];
|
|
87
|
+
const groupData = groupDetails.groups[groupName];
|
|
88
|
+
const packageIDs = Object.keys(groupData);
|
|
89
|
+
packageIDs.forEach(packageID => {
|
|
90
|
+
const testIDs = Object.keys(groupData[packageID]);
|
|
91
|
+
testIDs.forEach(testID => {
|
|
92
|
+
const testData = groupData[packageID][testID];
|
|
93
|
+
const {score, what} = testData;
|
|
94
|
+
const listItem = `<li>Package <code>${packageID}</code>, test <code>${testID}</code>, score ${score} (${what})</li>`;
|
|
95
|
+
groupListItems.push(listItem);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const groupList = [
|
|
99
|
+
'<ul>',
|
|
100
|
+
groupListItems.join('\n '),
|
|
101
|
+
'</ul>'
|
|
102
|
+
].join(joiner);
|
|
103
|
+
groupSummaryItems.push(groupP, groupList);
|
|
104
|
+
});
|
|
105
|
+
query.groupSummary = groupSummaryItems.join(joiner);
|
|
106
|
+
}
|
|
107
|
+
// Otherwise, i.e. if the score has no classified issues as components:
|
|
108
|
+
else {
|
|
109
|
+
// Add a paragraph stating this for the group summary to the query.
|
|
110
|
+
query.groupSummary = '<p>No classified issues contributed to the score.</p>'
|
|
111
|
+
}
|
|
112
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
spA11yMessage
|
|
3
|
+
Testilo score proc a11yMessage
|
|
4
|
+
|
|
5
|
+
Computes scores from Testaro script a11yMessage and adds them to a report.
|
|
6
|
+
Usage examples:
|
|
7
|
+
node score a11yMessage 35k1r
|
|
8
|
+
node score a11yMessage
|
|
9
|
+
|
|
10
|
+
This proc computes a score that is intended to represent how accessibly a web page offers
|
|
11
|
+
a user an opportunity to report an accessibility issue about that page. Scores can range
|
|
12
|
+
from 0 to 14.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// CONSTANTS
|
|
16
|
+
|
|
17
|
+
// ID of this proc.
|
|
18
|
+
const scoreProcID = 'a11ymessage';
|
|
19
|
+
|
|
20
|
+
// FUNCTIONS
|
|
21
|
+
|
|
22
|
+
// Scores the contact links of a type.
|
|
23
|
+
const contactScorer = (result, score, type) => {
|
|
24
|
+
const links = result.items;
|
|
25
|
+
score[type] += 1;
|
|
26
|
+
if (
|
|
27
|
+
links.some(
|
|
28
|
+
link => link.textContent.toLowerCase().includes('accessibility')
|
|
29
|
+
)
|
|
30
|
+
) {
|
|
31
|
+
score[type] += 2;
|
|
32
|
+
}
|
|
33
|
+
else if (
|
|
34
|
+
links.some(
|
|
35
|
+
link => link.parentTextContent.toLowerCase().includes('accessibility')
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
score[type] += 1;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Scores a report.
|
|
42
|
+
exports.scorer = async report => {
|
|
43
|
+
const {acts} = report;
|
|
44
|
+
report.score = {
|
|
45
|
+
page: 0,
|
|
46
|
+
a11yLink: 0,
|
|
47
|
+
title: 0,
|
|
48
|
+
heading: 0,
|
|
49
|
+
mailLink: 0,
|
|
50
|
+
telLink: 0,
|
|
51
|
+
total: 0
|
|
52
|
+
};
|
|
53
|
+
const {score} = report;
|
|
54
|
+
if (Array.isArray(acts)) {
|
|
55
|
+
// Act 1: page load.
|
|
56
|
+
if (acts[1].result.startsWith('http')) {
|
|
57
|
+
score.page = 2;
|
|
58
|
+
// Act 2: click an accessibility link.
|
|
59
|
+
if (acts[2].result.move === 'clicked') {
|
|
60
|
+
score.a11yLink = 2;
|
|
61
|
+
// Act 4: next page has an accessibility title.
|
|
62
|
+
const act4Result = acts[4].result;
|
|
63
|
+
if (act4Result && act4Result.toLowerCase().includes('accessibility')) {
|
|
64
|
+
score.title = 2;
|
|
65
|
+
// Act 5: page has exactly 1 h1 heading.
|
|
66
|
+
const act5Result = acts[5].result;
|
|
67
|
+
if (act5Result && act5Result.total === 1) {
|
|
68
|
+
score.heading = 1;
|
|
69
|
+
// Act 5: h1 is an accessibility heading.
|
|
70
|
+
if (act5Result.items[0].textContent.toLowerCase().includes('accessibility')) {
|
|
71
|
+
score.heading += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Act 6: page has accessibility email and telephone links.
|
|
76
|
+
const act6Result = acts[6].result;
|
|
77
|
+
if (act6Result.total) {
|
|
78
|
+
contactScorer(act6Result, score, 'mailLink');
|
|
79
|
+
}
|
|
80
|
+
// Act 7: page has accessibility email and telephone links.
|
|
81
|
+
const act7Result = acts[7].result;
|
|
82
|
+
if (act7Result.total) {
|
|
83
|
+
contactScorer(act7Result, score, 'telLink');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
score.total = score.page
|
|
89
|
+
+ score.a11yLink
|
|
90
|
+
+ score.title
|
|
91
|
+
+ score.heading
|
|
92
|
+
+ score.mailLink
|
|
93
|
+
+ score.telLink;
|
|
94
|
+
};
|
package/score.js
CHANGED
|
@@ -38,7 +38,7 @@ const score = async () => {
|
|
|
38
38
|
const reportJSON = await fs.readFile(`${reportDirRawAbs}/${fileName}`, 'utf8');
|
|
39
39
|
const report = JSON.parse(reportJSON);
|
|
40
40
|
await scorer(report);
|
|
41
|
-
report.
|
|
41
|
+
report.scoreProcID = scoreProcID;
|
|
42
42
|
// Write it to a file.
|
|
43
43
|
const scoredReportJSON = JSON.stringify(report, null, 2);
|
|
44
44
|
await fs.writeFile(`${__dirname}/${reportDirScored}/${fileName}`, `${scoredReportJSON}\n`);
|