testilo 3.9.16 → 3.10.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.
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
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>Transactional accessibility comparison</h1>
|
|
19
|
+
</header>
|
|
20
|
+
<h2>Introduction</h2>
|
|
21
|
+
<p>This report compares __pageCount__ web pages, rating each page on <dfn>transactional accessibility</dfn>. The score given to each page estimates the <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a> of one particular transaction. In it, the user finds the price at which a product is offered for sale on the website to which the page belongs.</p>
|
|
22
|
+
<p>You can find a more detailed explanation of transactional accessibility and how the accessibility of this particular transaction is scored in each of the digests that this report is derived from. The scores in the table below are links to those digests.</p>
|
|
23
|
+
<p>This comparative report was produced by the <code>cpProductPrice</code> procedure of <a href="https://www.npmjs.com/package/testilo">Testilo</a>.</p>
|
|
24
|
+
<p>The scores in the table below could range from 0 (complete failure) up to 26 (complete success).</p>
|
|
25
|
+
<h2>Comparison</h2>
|
|
26
|
+
<table class="allBorder">
|
|
27
|
+
<caption>Accessibility scores of product-price-inquiry transactions</caption>
|
|
28
|
+
<thead>
|
|
29
|
+
<tr><th scope="col">Page</th><th scope="col" colspan="2">Score (higher is better)</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody class="linkSmaller secondCellRight">
|
|
32
|
+
__tableBody__
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
<footer>
|
|
36
|
+
<p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
|
|
37
|
+
</footer>
|
|
38
|
+
</main>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
cpProductPrice.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
|
+
// Returns data on the hosts in the report directory.
|
|
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, host, score} = file;
|
|
31
|
+
bodyData.push({
|
|
32
|
+
id,
|
|
33
|
+
org: host.what,
|
|
34
|
+
url: host.which,
|
|
35
|
+
score: score.total
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
pageCount,
|
|
40
|
+
bodyData
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
// Returns 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) => b.score - a.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
|
+
};
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
Computes scores from Testaro script a11yMessage and adds them to a report.
|
|
6
6
|
Usage examples:
|
|
7
|
-
node score
|
|
8
|
-
node score
|
|
7
|
+
node score spA11yMessage 35k1r
|
|
8
|
+
node score spA11yMessage
|
|
9
9
|
|
|
10
10
|
This proc computes a score that is intended to represent how accessibly a web page offers
|
|
11
11
|
a user an opportunity to report an accessibility issue about that page. Scores can range
|
|
12
|
-
from perfect 0 to
|
|
12
|
+
from perfect 0 to 23.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// CONSTANTS
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
spProductPrice
|
|
3
|
+
Testilo score proc productPrice
|
|
4
|
+
|
|
5
|
+
Computes scores from Testaro script productPrice and adds them to a report.
|
|
6
|
+
Usage examples:
|
|
7
|
+
node score spProductPrice 35k1r
|
|
8
|
+
node score spProductPrice
|
|
9
|
+
|
|
10
|
+
This proc computes a score that is intended to represent how accessibly a website offers
|
|
11
|
+
a user an opportunity to determine the price at which the website owner offers a product.
|
|
12
|
+
Scores can range from perfect 0 to 16.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// CONSTANTS
|
|
16
|
+
|
|
17
|
+
// ID of this proc.
|
|
18
|
+
const scoreProcID = 'productprice';
|
|
19
|
+
|
|
20
|
+
// FUNCTIONS
|
|
21
|
+
|
|
22
|
+
// Returns whether a text contains a U.S. price.
|
|
23
|
+
const hasPrice = (text, isAll) => {
|
|
24
|
+
const matcher = '\\$ ?\\d*(?:,\\d{3})?(?:\\.\\d{2})?(?: *USD)?';
|
|
25
|
+
const trimText = text.trim();
|
|
26
|
+
if (isAll) {
|
|
27
|
+
return new RegExp(`^${matcher}$`).test(trimText);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return new RegExp(matcher).test(trimText);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
// Recursively inspects a subtree for semantic marking.
|
|
34
|
+
const findPriceProp = (root, score) => {
|
|
35
|
+
// If no semantically marked price has been found yet:
|
|
36
|
+
if (! score.priceProp) {
|
|
37
|
+
// If the text of the root is exactly a price:
|
|
38
|
+
if (hasPrice(root.text, true)) {
|
|
39
|
+
// If it semantically marks the price as such:
|
|
40
|
+
if (root.attributes.some(
|
|
41
|
+
attribute => attribute.name === 'itemprop' && attribute.value === 'price'
|
|
42
|
+
)) {
|
|
43
|
+
// Add a score and stop looking.
|
|
44
|
+
score.priceProp = 3;
|
|
45
|
+
}
|
|
46
|
+
// Otherwise, i.e. if it does not semantically mark the price:
|
|
47
|
+
else {
|
|
48
|
+
// Inspect the children of the root.
|
|
49
|
+
root.children.forEach(child => {
|
|
50
|
+
findPriceProp(child);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// Scores a report.
|
|
57
|
+
exports.scorer = async report => {
|
|
58
|
+
const {acts} = report;
|
|
59
|
+
report.scoreProcID = scoreProcID;
|
|
60
|
+
report.score = {
|
|
61
|
+
pageLoad: 0,
|
|
62
|
+
pageFast: 0,
|
|
63
|
+
searchInput: 0,
|
|
64
|
+
searchType: 0,
|
|
65
|
+
searchWork: 0,
|
|
66
|
+
searchFast: 0,
|
|
67
|
+
nameInPage: 0,
|
|
68
|
+
nameInNode: 0,
|
|
69
|
+
nameProp: 0,
|
|
70
|
+
price: 0,
|
|
71
|
+
priceProximity: 0,
|
|
72
|
+
priceProp: 0
|
|
73
|
+
};
|
|
74
|
+
const {score} = report;
|
|
75
|
+
if (Array.isArray(acts)) {
|
|
76
|
+
// Act 1: If the page loaded:
|
|
77
|
+
if (acts[1].result.startsWith('http')) {
|
|
78
|
+
score.pageLoad = 2;
|
|
79
|
+
// Score how fast it loaded.
|
|
80
|
+
const loadTime = acts[1].endTime - acts[1].startTime;
|
|
81
|
+
if (loadTime < 4000) {
|
|
82
|
+
score.pageFast = 2;
|
|
83
|
+
}
|
|
84
|
+
else if (loadTime < 6000) {
|
|
85
|
+
score.pageFast = 1;
|
|
86
|
+
}
|
|
87
|
+
// Act 2: If the page has a search input:
|
|
88
|
+
const {result} = acts[2];
|
|
89
|
+
if (result.found) {
|
|
90
|
+
score.searchInput = 1;
|
|
91
|
+
// If it is a search-type input:
|
|
92
|
+
if (result.attributes && result.attributes.type === 'search') {
|
|
93
|
+
score.searchType = 2;
|
|
94
|
+
}
|
|
95
|
+
// If it works:
|
|
96
|
+
if (result.success) {
|
|
97
|
+
score.searchWork = 2;
|
|
98
|
+
// Score how fast it works.
|
|
99
|
+
const loadTime = acts[2].endTime - acts[2].startTime;
|
|
100
|
+
if (loadTime < 3000) {
|
|
101
|
+
score.searchFast = 3;
|
|
102
|
+
}
|
|
103
|
+
else if (loadTime < 5000) {
|
|
104
|
+
score.searchFast = 2;
|
|
105
|
+
}
|
|
106
|
+
else if (loadTime < 7000) {
|
|
107
|
+
score.searchFast = 1;
|
|
108
|
+
}
|
|
109
|
+
// Act 3: If the product is named on the result page:
|
|
110
|
+
let {result} = acts[3];
|
|
111
|
+
if (result && result.found) {
|
|
112
|
+
score.nameInPage = 2;
|
|
113
|
+
// Act 4: If the product is named by any text node:
|
|
114
|
+
result = acts[4].result;
|
|
115
|
+
if (result && result.nodeCount) {
|
|
116
|
+
score.nameInNode = 2;
|
|
117
|
+
// If any such text node semantically marks the name:
|
|
118
|
+
if (result.items.some(item => {
|
|
119
|
+
const parent = item.ancestors[0];
|
|
120
|
+
return parent.attributes.some(
|
|
121
|
+
attribute => attribute.name === 'itemprop' && attribute.value === 'name'
|
|
122
|
+
);
|
|
123
|
+
})) {
|
|
124
|
+
score.nameProp = 3;
|
|
125
|
+
}
|
|
126
|
+
// Act 4: If a price appears in the text content of an ancestor:
|
|
127
|
+
const priceInContext = result.items.some(
|
|
128
|
+
item => item.ancestors.some(
|
|
129
|
+
ancestor => ancestor.text && hasPrice(ancestor.text, false)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
if (priceInContext) {
|
|
133
|
+
score.price = 1;
|
|
134
|
+
// Act 4: Proximity and semantic specification of a price.
|
|
135
|
+
let priceDistance = Infinity;
|
|
136
|
+
// For each text node containing the product name:
|
|
137
|
+
result.items.forEach(item => {
|
|
138
|
+
// Get the distance to the nearest ancestor with a price in its text content.
|
|
139
|
+
const itemPriceDistance = item.ancestors.findIndex(
|
|
140
|
+
ancestor => ancestor.text && hasPrice(ancestor.text, false)
|
|
141
|
+
);
|
|
142
|
+
// If that distance is less than the smallest one found yet:
|
|
143
|
+
if (itemPriceDistance > -1 && itemPriceDistance < priceDistance) {
|
|
144
|
+
// Update the smallest one found.
|
|
145
|
+
priceDistance = itemPriceDistance;
|
|
146
|
+
// Inspect its subtree for a semantically marked price.
|
|
147
|
+
findPriceProp(item, score);
|
|
148
|
+
}
|
|
149
|
+
// Update the price-proximity score.
|
|
150
|
+
score.priceProximity = Math.max(0, 6 - priceDistance);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
score.total = Object.values(score).reduce((total, current) => total + current);
|
|
160
|
+
};
|