testilo 38.0.2 → 39.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -786,10 +786,14 @@ To test the `digest` module, in the project directory you can execute the statem
786
786
 
787
787
  ### Summarization
788
788
 
789
- The `summarize` module of Testilo can summarize a scored report. The summary is an object that contains these properties from the report: `id`, `endTime`, `sources`, and `score` (the value of the `score.total` property of the report).
789
+ The `summarize` module of Testilo can summarize a scored report. The summary is an object that contains these properties from the report: `id`, `endTime`, `targetWhat` (description of the target), `url` (of the target), `sources`, and `score` (only the value of the `score.total` property of the report).
790
+
791
+ Report summaries make some operations more efficient by allowing other modules to get needed data from summaries instead of from reports. The size of a summary tends to be about 0.01% of the size of a report.
790
792
 
791
793
  #### Invocation
792
794
 
795
+ The `summarize` module summarizes one report when invoked by a module, but the `call` module invoked by a user can call `summarize` multiple times to summarize multiple reports and combine those summaries into a file.
796
+
793
797
  ##### By a module
794
798
 
795
799
  A module can invoke `summarize()` in this way:
@@ -819,10 +823,6 @@ When a user invokes `summarize` in this example, the `call` module:
819
823
  - creates a _summary report_, an object containing three properties: `id` (an ID), `what` (a description, such as `'divisions'`), and `summaries` (the array of summaries).
820
824
  - writes the summary report in JSON format to the `summarized` subdirectory of the `REPORTDIR` directory, using the ID as the base of the file name.
821
825
 
822
- #### Summary reports
823
-
824
- A summary report serves as a necessary input to the `compare` and `track` modules described below. When a user invokes the `compare` module, a summary report is produced. A module can create a summary report by invoking `compare` multiple times on different scored reports, assembling the resulting summaries into an array, and creating an object like the one the `call` module creates for a user.
825
-
826
826
  ### Comparison
827
827
 
828
828
  If you use Testilo to perform a battery of tests on multiple targets, you may want a single report that compares the total scores received by the targets. Testilo can produce such a _comparison_.
@@ -833,7 +833,7 @@ The `compare` module compares the scores in a summary report. The `compare()` fu
833
833
 
834
834
  The comparison function defines the rules for generating an HTML file comparing the scored reports. The Testilo package contains a `procs/compare` directory, in which there are subdirectories containing modules that export comparison functions. You can use one of those functions, or you can create your own.
835
835
 
836
- Summary reports suitable for comparisons are those that contain one result per target. If a summary report contains results from multiple times per target, tracking (described below) is appropriate, rather than comparison.
836
+ The built-in comparison functions compare all of the scores in the summary report. Thus, if the summary report contains multiple scores for the same target, based on tests performed at various times, those scores will all appear in the comparison, labeled identically with the `what` description of the target. If you want only one score per target to appear, you can create a new summary report that includes only one summary per target in its `summaries` array.
837
837
 
838
838
  #### Invocation
839
839
 
@@ -864,7 +864,7 @@ node call compare 'state legislators' tcp99 240813
864
864
 
865
865
  When a user invokes `compare` in this example, the `call` module:
866
866
  - gets the comparison module from subdirectory `tcp99` of the subdirectory `compare` in the `FUNCTIONDIR` directory.
867
- - gets the first summary report whose file name begins with `'240813'` from the `summarized` subdirectory of the `REPORTDIR` directory.
867
+ - gets the last summary report whose file name begins with `'240813'` from the `summarized` subdirectory of the `REPORTDIR` directory.
868
868
  - creates an ID for the comparison.
869
869
  - creates the comparison as an HTML document.
870
870
  - writes the comparison in the `comparative` subdirectory of the `REPORTDIR` directory, with `state legislators` as a description and the ID as the base of the file name.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "38.0.2",
3
+ "version": "39.0.1",
4
4
  "description": "Prepares Testaro jobs and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -0,0 +1,42 @@
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: __what__</title>
12
+ <link rel="icon" href="favicon.ico">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility score comparison: __what__</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>This is comparison __id__.</p>
22
+ <p>The table below compares __pageCount__ web pages on <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a>. The page names are links to the pages on the web. The scores are links to digests that explain in detail how the scores were computed.</p>
23
+ <p>The pages were tested by <a href="https://www.npmjs.com/package/testaro">Testaro</a>. Testaro used ten tools (Alfa, ASLint, Axe, Editoria11y, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Testaro, and WAVE) to perform about 900 automated accessibility tests.</p>
24
+ <p><a href="https://www.npmjs.com/package/testilo">Testilo</a> classified the problems found by these tests into <dfn>issues</dfn> and assigned a <dfn>score</dfn> to each page. A perfect score would be 0. Higher scores indicate more issues, more instances of them, more serious issues, and more of the tools reporting instances of issues.</p>
25
+ <h2>Comparison</h2>
26
+ <table class="allBorder redBar">
27
+ <caption>Accessibility scores of web pages</caption>
28
+ <thead>
29
+ <tr><th scope="col">Page</th><th scope="col" colspan="2">Score (from best to worst)</tr>
30
+ </thead>
31
+ <tbody class="linkSmaller secondCellRight">
32
+ __tableBody__
33
+ </tbody>
34
+ </table>
35
+ <h2>Disclaimer</h2>
36
+ <p>Other procedures would yield different scores and rank orders among these pages.</p>
37
+ <footer>
38
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
39
+ </footer>
40
+ </main>
41
+ </body>
42
+ </html>
@@ -0,0 +1,84 @@
1
+ /*
2
+ © 2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
21
+ */
22
+
23
+ /*
24
+ index
25
+ Compares scores in a summary report.
26
+ */
27
+
28
+ // ########## IMPORTS
29
+
30
+ // Module to access files.
31
+ const fs = require('fs/promises');
32
+ const {getBarCell, getNowDate, getNowDateSlash} = require('../../util');
33
+
34
+ // CONSTANTS
35
+
36
+ // Newlines with indentations.
37
+ const innestJoiner = '\n ';
38
+
39
+ // ########## FUNCTIONS
40
+
41
+ // Returns the maximum score.
42
+ const getMaxScore = summaryReport => summaryReport.summaries.reduce(
43
+ (max, result) => Math.max(max, result.score), 0
44
+ );
45
+ // Converts summary report data to a table body.
46
+ const getTableBody = async summaryReport => {
47
+ const maxScore = getMaxScore(summaryReport);
48
+ const rows = summaryReport.summaries
49
+ .sort((a, b) => a.score - b.score)
50
+ .map(result => {
51
+ const {id, score, targetWhat, url} = result;
52
+ const pageCell = `<th scope="row"><a href="${url}">${targetWhat}</a></th>`;
53
+ const scoreDestination = process.env.DIGEST_URL.replace('__id__', id);
54
+ const numCell = `<td class="num"><a href="${scoreDestination}">${score}</a></td>`;
55
+ // Make the bar width proportional.
56
+ const barCell = getBarCell(score, maxScore, 25, false);
57
+ const row = `<tr>${pageCell}${numCell}${barCell}</tr>`;
58
+ return row;
59
+ });
60
+ return rows.join(innestJoiner);
61
+ };
62
+ // Populates a query for a comparative table.
63
+ const populateQuery = exports.populateQuery = async (id, what, summaryReport, query) => {
64
+ query.id = id;
65
+ query.what = what;
66
+ query.pageCount = summaryReport.summaries.length;
67
+ query.tableBody = await getTableBody(summaryReport);
68
+ query.dateISO = getNowDate();
69
+ query.dateSlash = getNowDateSlash();
70
+ };
71
+ // Returns a comparison.
72
+ exports.comparer = async (id, what, summaryReport) => {
73
+ // Create a query to replace placeholders.
74
+ const query = {};
75
+ populateQuery(id, what, summaryReport, query);
76
+ // Get the template.
77
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
78
+ // Replace its placeholders.
79
+ Object.keys(query).forEach(param => {
80
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
81
+ });
82
+ // Return the comparison.
83
+ return template;
84
+ };
@@ -53,12 +53,10 @@ const populateQuery = async (id, what, summaryReport, query) => {
53
53
  // JSON of summary report.
54
54
  const {summaries} = summaryReport;
55
55
  query.summaryReportJSON = JSON.stringify(summaryReport);
56
- // Legend.
57
-
58
56
  // Get an array of target descriptions and assign to each an ID.
59
57
  const rows = [];
60
58
  const targets = Array
61
- .from(new Set(summaries.map(result => result.target.what)))
59
+ .from(new Set(summaries.map(result => result.targetWhat)))
62
60
  .sort()
63
61
  .map((targetWhat, index) => [alphaNumOf(index), targetWhat]);
64
62
  const targetIDs = {};
@@ -0,0 +1,116 @@
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 web pages">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility tracking report</title>
12
+ <link rel="icon" href="favicon.ico">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility tracking report</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>This is tracking report <code>__id__</code>, for __what__.</p>
22
+ <p>It tracks accessibility scores over time. A perfect score is 0. The tracking was performed by Testilo procedure <code>__tp__</code>.</p>
23
+ <p>The results are presented first as a graph, and then as a table.</p>
24
+ <h2>Results as a graph</h2>
25
+ <h3>Legend</h3>
26
+ <ul id="legendItems">
27
+ __legendItems__
28
+ </ul>
29
+ <h3>Line graph</h3>
30
+ <figure id="graph">
31
+ <figcaption>Accessibility scores</figcaption>
32
+ </figure>
33
+ <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
35
+ <script type="module" defer>
36
+ const summaryReportJSON = '__summaryReportJSON__';
37
+ const summaryReport = JSON.parse(summaryReportJSON);
38
+ const graphData = [];
39
+ const targetIDs = {};
40
+ Array.from(document.getElementById('legendItems').children).forEach(li => {
41
+ const targetData = li.textContent.split(': ');
42
+ targetIDs[targetData[1]] = targetData[0];
43
+ });
44
+ summaryReport.summaries.forEach(result => {
45
+ const {what} = result.sources.target;
46
+ graphData.push({
47
+ targetID: targetIDs[what],
48
+ targetWhat: what,
49
+ time: new Date(`20${result.endTime}Z`),
50
+ score: result.score
51
+ });
52
+ });
53
+ const svg = Plot.plot({
54
+ style: 'overflow: visible;',
55
+ height: 600,
56
+ y: {grid: true},
57
+ marks: [
58
+ Plot.ruleY([0]),
59
+ Plot.lineY(graphData, {
60
+ x: 'time',
61
+ y: 'score',
62
+ stroke: 'targetID'
63
+ }),
64
+ Plot.dot(graphData, {
65
+ x: 'time',
66
+ y: 'score',
67
+ z: 'targetID',
68
+ r: 9
69
+ }),
70
+ Plot.text(graphData, {
71
+ x: 'time',
72
+ y: 'score',
73
+ z: 'targetID',
74
+ text: 'targetID',
75
+ textAnchor: 'middle'
76
+ }),
77
+ Plot.text(graphData, Plot.selectFirst({
78
+ x: 'time',
79
+ y: 'score',
80
+ z: 'targetID',
81
+ text: 'targetWhat',
82
+ textAnchor: 'start',
83
+ dx: 15
84
+ })),
85
+ Plot.text(graphData, Plot.selectLast({
86
+ x: 'time',
87
+ y: 'score',
88
+ z: 'targetID',
89
+ text: 'targetWhat',
90
+ textAnchor: 'start',
91
+ dx: 15
92
+ }))
93
+ ]
94
+ });
95
+ document.getElementById('graph').insertAdjacentElement('beforeend', svg);
96
+ </script>
97
+ <h2>Results as a table</h2>
98
+ <table class="allBorder secondCellRight">
99
+ <caption>Accessibility scores</caption>
100
+ <thead>
101
+ <tr>
102
+ <th>Date and time</th>
103
+ <th>Score</th>
104
+ <th>Target</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ __scoreRows__
109
+ </tbody>
110
+ </table>
111
+ <footer>
112
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
113
+ </footer>
114
+ </main>
115
+ </body>
116
+ </html>
@@ -0,0 +1,100 @@
1
+ /*
2
+ © 2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
21
+ */
22
+
23
+ // index: tracker for tracking procedure ttp43.
24
+
25
+ // IMPORTS
26
+
27
+ // Module to keep secrets.
28
+ require('dotenv').config();
29
+ // Module to process files.
30
+ const fs = require('fs/promises');
31
+ // Utility module.
32
+ const {alphaNumOf, getNowDate, getNowDateSlash} = require('../../util');
33
+
34
+ // CONSTANTS
35
+
36
+ // Tracker ID.
37
+ const trackerID = 'ttp43';
38
+ // Newline with indentations.
39
+ const innerJoiner = '\n ';
40
+ // Digest URL.
41
+ const digestURL = process.env.DIGEST_URL;
42
+
43
+ // FUNCTIONS
44
+
45
+ // Adds parameters to a query for a tracking report.
46
+ const populateQuery = async (id, what, summaryReport, query) => {
47
+ // General parameters.
48
+ query.id = id;
49
+ query.what = what;
50
+ query.tp = trackerID;
51
+ query.dateISO = getNowDate();
52
+ query.dateSlash = getNowDateSlash();
53
+ // JSON of summary report.
54
+ const {summaries} = summaryReport;
55
+ query.summaryReportJSON = JSON.stringify(summaryReport);
56
+ // Get an array of target descriptions and assign to each an ID.
57
+ const rows = [];
58
+ const targets = Array
59
+ .from(new Set(summaries.map(result => result.targetWhat)))
60
+ .sort()
61
+ .map((targetWhat, index) => [alphaNumOf(index), targetWhat]);
62
+ const targetIDs = {};
63
+ targets.forEach(target => {
64
+ targetIDs[target[1]] = target[0];
65
+ });
66
+ // Add legend items to the query.
67
+ const legendItems = targets.map(target => `<li>${target[0]}: ${target[1]}</li>`);
68
+ query.legendItems = legendItems.join('\n ');
69
+ // For each result:
70
+ summaries.forEach(result => {
71
+ const {endTime, id, score, targetWhat, url} = result;
72
+ // Create a date-time cell.
73
+ const timeCell = `<td>${endTime}</td>`;
74
+ // Create a score cell.
75
+ const digestLinkDestination = digestURL.replace('__id__', id);
76
+ const scoreCell = `<td><a href=${digestLinkDestination}>${score}</a></td>`;
77
+ // Create a target cell.
78
+ const targetLink = `<a href="${url}">${targetWhat}</a>`;
79
+ const targetCell = `<td>${targetIDs[targetWhat]}: ${targetLink}</td>`;
80
+ const row = `<tr>${[timeCell, scoreCell, targetCell].join('')}</tr>`;
81
+ // Add the row to the array of rows.
82
+ rows.push(row);
83
+ });
84
+ // Add the rows to the query.
85
+ query.scoreRows = rows.join(innerJoiner);
86
+ };
87
+ // Returns a tracking report.
88
+ exports.tracker = async (id, what, summaryReport) => {
89
+ // Create a query to replace placeholders.
90
+ const query = {};
91
+ await populateQuery(id, what, summaryReport, query);
92
+ // Get the template.
93
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
94
+ // Replace its placeholders.
95
+ Object.keys(query).forEach(param => {
96
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
97
+ });
98
+ // Return the tracking report.
99
+ return template;
100
+ };
package/summarize.js CHANGED
@@ -34,9 +34,12 @@ require('dotenv').config();
34
34
 
35
35
  // Returns a report summary.
36
36
  exports.summarize = report => {
37
- const {id, jobData, score, sources} = report;
37
+ const {id, jobData, score, sources, target} = report;
38
+ const foundTarget = target || sources.target;
38
39
  const summary = {
39
40
  id: id || null,
41
+ url: foundTarget && (foundTarget.url || foundTarget.which) || null,
42
+ targetWhat: foundTarget.what || null,
40
43
  endTime: jobData && jobData.endTime || null,
41
44
  sources: sources || null,
42
45
  score: score && score.summary && score.summary.total || null